@punkestu/react-html 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +72 -15
  2. package/dist/cli/docs-content.d.ts +8 -0
  3. package/dist/cli/docs-content.d.ts.map +1 -0
  4. package/dist/cli/docs-content.js +328 -0
  5. package/dist/cli/docs-content.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +135 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/tui-docs.d.ts +5 -0
  11. package/dist/cli/tui-docs.d.ts.map +1 -0
  12. package/dist/cli/tui-docs.js +191 -0
  13. package/dist/cli/tui-docs.js.map +1 -0
  14. package/dist/client/hydrate.d.ts +4 -0
  15. package/dist/client/hydrate.d.ts.map +1 -0
  16. package/dist/client/hydrate.js +80 -0
  17. package/dist/client/hydrate.js.map +1 -0
  18. package/dist/core/bundler.d.ts +9 -0
  19. package/dist/core/bundler.d.ts.map +1 -0
  20. package/dist/core/bundler.js +245 -0
  21. package/dist/core/bundler.js.map +1 -0
  22. package/dist/core/formatter.d.ts +6 -0
  23. package/dist/core/formatter.d.ts.map +1 -0
  24. package/dist/core/formatter.js +74 -0
  25. package/dist/core/formatter.js.map +1 -0
  26. package/dist/core/island-client.d.ts +9 -0
  27. package/dist/core/island-client.d.ts.map +1 -0
  28. package/dist/core/island-client.js +8 -0
  29. package/dist/core/island-client.js.map +1 -0
  30. package/dist/core/island-discovery.d.ts +19 -0
  31. package/dist/core/island-discovery.d.ts.map +1 -0
  32. package/dist/core/island-discovery.js +92 -0
  33. package/dist/core/island-discovery.js.map +1 -0
  34. package/dist/core/island-registry.d.ts +15 -0
  35. package/dist/core/island-registry.d.ts.map +1 -0
  36. package/dist/core/island-registry.js +17 -0
  37. package/dist/core/island-registry.js.map +1 -0
  38. package/dist/core/island.d.ts +32 -0
  39. package/dist/core/island.d.ts.map +1 -0
  40. package/dist/core/island.js +47 -0
  41. package/dist/core/island.js.map +1 -0
  42. package/dist/core/loader.d.ts +11 -0
  43. package/dist/core/loader.d.ts.map +1 -0
  44. package/dist/core/loader.js +87 -0
  45. package/dist/core/loader.js.map +1 -0
  46. package/dist/core/renderer.d.ts +20 -0
  47. package/dist/core/renderer.d.ts.map +1 -0
  48. package/dist/core/renderer.js +53 -0
  49. package/dist/core/renderer.js.map +1 -0
  50. package/dist/index.d.ts +27 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +110 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/server/dev-server.d.ts +8 -0
  55. package/dist/server/dev-server.d.ts.map +1 -0
  56. package/dist/server/dev-server.js +112 -0
  57. package/dist/server/dev-server.js.map +1 -0
  58. package/dist/server/engine.d.ts +50 -0
  59. package/dist/server/engine.d.ts.map +1 -0
  60. package/dist/server/engine.js +225 -0
  61. package/dist/server/engine.js.map +1 -0
  62. package/{src/server/index.ts → dist/server/index.d.ts} +1 -1
  63. package/dist/server/index.d.ts.map +1 -0
  64. package/dist/server/index.js +3 -0
  65. package/dist/server/index.js.map +1 -0
  66. package/package.json +5 -1
  67. package/.react-html/cache/page-fefe905ae5d2.mjs +0 -9
  68. package/DOCS.md +0 -317
  69. package/example/public/styles.css +0 -184
  70. package/example/src/components/Card.tsx +0 -17
  71. package/example/src/components/Counter.island.tsx +0 -35
  72. package/example/src/components/Footer.tsx +0 -13
  73. package/example/src/components/Header.tsx +0 -20
  74. package/example/src/components/Layout.tsx +0 -39
  75. package/example/src/components/Tabs.island.tsx +0 -59
  76. package/example/src/pages/index.tsx +0 -49
  77. package/example/src/pages/pure.tsx +0 -56
  78. package/src/cli/docs-content.ts +0 -268
  79. package/src/cli/index.ts +0 -113
  80. package/src/cli/tui-docs.ts +0 -214
  81. package/src/client/hydrate.ts +0 -84
  82. package/src/core/bundler.ts +0 -122
  83. package/src/core/formatter.ts +0 -92
  84. package/src/core/island-client.ts +0 -23
  85. package/src/core/island-registry.ts +0 -35
  86. package/src/core/island.tsx +0 -110
  87. package/src/core/loader.ts +0 -89
  88. package/src/core/renderer.ts +0 -80
  89. package/src/index.ts +0 -138
  90. package/src/server/dev-server.ts +0 -125
  91. package/src/server/engine.ts +0 -270
  92. package/tests/engine.test.mjs +0 -288
  93. package/tsconfig.json +0 -21
package/DOCS.md DELETED
@@ -1,317 +0,0 @@
1
- # 📖 react-html Complete Manual & Documentation
2
-
3
- A high-performance static engine to compile React/JSX to **pure, semantic HTML** with selective reactive compilation for interactive components via an **Islands Architecture**.
4
-
5
- > 💡 **Terminal UI Access**: You can also browse this documentation interactively in your terminal by running:
6
- > ```bash
7
- > npx react-html docs
8
- > # Or print a specific section:
9
- > npx react-html docs islands
10
- > ```
11
-
12
- ---
13
-
14
- ## Table of Contents
15
-
16
- 1. [Overview & Philosophy](#1-overview--philosophy)
17
- 2. [Getting Started & Setup](#2-getting-started--setup)
18
- 3. [Modular HTML with React](#3-modular-html-with-react)
19
- 4. [Reactive Islands Architecture](#4-reactive-islands-architecture)
20
- 5. [Hydration Directives](#5-hydration-directives)
21
- 6. [CLI Commands & Options](#6-cli-commands--options)
22
- 7. [Programmatic API Reference](#7-programmatic-api-reference)
23
- 8. [Server App Integration (Express, Fastify, Node HTTP)](#8-server-app-integration-express-fastify-node-http)
24
- 9. [Best Practices & Troubleshooting](#9-best-practices--troubleshooting)
25
-
26
- ---
27
-
28
- ## 1. Overview & Philosophy
29
-
30
- Modern web development often forces a painful choice:
31
- - **Write raw HTML/handlebars/nunjucks**: You lose type safety, component modularity, and rich JSX composition.
32
- - **Use modern React frameworks (Next.js, CRA, Remix)**: You inadvertently ship massive megabytes of JavaScript, client routers, and heavy hydration runtimes even for mostly static pages.
33
-
34
- **`react-html` gives you the best of both worlds**:
35
- 1. **Zero JavaScript by Default**:
36
- Pages without interactive islands output 100% pure, clean HTML5. No client-side React runtime, no `data-reactroot` attributes, and 0 KB JavaScript sent to visitors.
37
- 2. **Modular & DRY Components**:
38
- Compose your pages with `<Layout>`, `<Header>`, `<Card>`, `<Footer>`, loops, and conditionals using standard React and TypeScript.
39
- 3. **Selective Reactivity**:
40
- Interactive components (counters, carousels, modals, filters) are marked as **Islands**. The engine pre-renders their initial markup for instant paint and bundles only that component into a standalone client script using `esbuild`.
41
-
42
- ---
43
-
44
- ## 2. Getting Started & Setup
45
-
46
- ### Prerequisites
47
- - Standard **Node.js v20.0.0 or higher** with `npm` (no Bun dependencies).
48
-
49
- ### Installation
50
- ```bash
51
- npm install react-html
52
- ```
53
-
54
- Or clone and link locally:
55
- ```bash
56
- git clone https://github.com/microapes/react-html.git
57
- cd react-html
58
- npm install
59
- npm run build
60
- npm link
61
- ```
62
-
63
- ### Recommended Directory Structure
64
-
65
- ```text
66
- my-project/
67
- ├── package.json
68
- ├── public/ # Static files copied to dist/ verbatim
69
- │ ├── favicon.ico
70
- │ └── styles.css
71
- └── src/
72
- ├── components/ # Reusable modular UI components
73
- │ ├── Layout.tsx # Master HTML wrapper (<head>, <body>)
74
- │ ├── Header.tsx # Navigation header
75
- │ ├── Footer.tsx # Footer component
76
- │ └── Counter.island.tsx # Interactive island component
77
- └── pages/ # Page entrypoints
78
- ├── index.tsx # -> dist/index.html
79
- ├── about.tsx # -> dist/about.html
80
- └── docs/
81
- └── guide.tsx # -> dist/docs/guide.html
82
- ```
83
-
84
- ---
85
-
86
- ## 3. Modular HTML with React
87
-
88
- Eliminate repetitive HTML across your project by creating reusable layout components:
89
-
90
- ```tsx
91
- // src/components/Layout.tsx
92
- import React, { ReactNode } from 'react';
93
- import { Header } from './Header.js';
94
- import { Footer } from './Footer.js';
95
-
96
- export interface LayoutProps {
97
- title?: string;
98
- description?: string;
99
- children: ReactNode;
100
- }
101
-
102
- export function Layout({
103
- title = 'Default Title',
104
- description = 'My static site',
105
- children
106
- }: LayoutProps) {
107
- return (
108
- <html lang="en">
109
- <head>
110
- <meta charSet="UTF-8" />
111
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
112
- <title>{title}</title>
113
- <meta name="description" content={description} />
114
- <link rel="stylesheet" href="/styles.css" />
115
- </head>
116
- <body>
117
- <Header />
118
- <main className="container">{children}</main>
119
- <Footer />
120
- </body>
121
- </html>
122
- );
123
- }
124
- ```
125
-
126
- Now, every page simply wraps its content without repeating `<head>`, stylesheets, or `<header>`/`<footer>`:
127
-
128
- ```tsx
129
- // src/pages/about.tsx
130
- import React from 'react';
131
- import { Layout } from '../components/Layout.js';
132
-
133
- export const title = 'About Us';
134
-
135
- export default function AboutPage() {
136
- return (
137
- <Layout title={title}>
138
- <h1>About Our Project</h1>
139
- <p>This entire page compiles into pure static HTML with 0 KB JavaScript!</p>
140
- </Layout>
141
- );
142
- }
143
- ```
144
-
145
- ---
146
-
147
- ## 4. Reactive Islands Architecture
148
-
149
- When a component needs client-side state (`useState`, `useEffect`, event listeners):
150
-
151
- ### Step 1: Wrap Component with `island()`
152
-
153
- ```tsx
154
- // src/components/Counter.island.tsx
155
- import React, { useState } from 'react';
156
- import { island } from 'react-html';
157
-
158
- export interface CounterProps {
159
- initialCount?: number;
160
- step?: number;
161
- }
162
-
163
- function CounterComponent({ initialCount = 0, step = 1 }: CounterProps) {
164
- const [count, setCount] = useState(initialCount);
165
-
166
- return (
167
- <div className="counter">
168
- <p>Current: {count}</p>
169
- <button onClick={() => setCount(c => c - step)}>- {step}</button>
170
- <button onClick={() => setCount(c => c + step)}>+ {step}</button>
171
- </div>
172
- );
173
- }
174
-
175
- // Wrap with island helper providing Component, source path, and identifier
176
- export const Counter = island(CounterComponent, import.meta.url, 'Counter');
177
- export default Counter;
178
- ```
179
-
180
- ### Step 2: Use in Any Page
181
-
182
- ```tsx
183
- // src/pages/index.tsx
184
- import React from 'react';
185
- import { Layout } from '../components/Layout.js';
186
- import { Counter } from '../components/Counter.island.js';
187
-
188
- export default function HomePage() {
189
- return (
190
- <Layout title="Home">
191
- <h1>Interactive Page</h1>
192
- {/* Pre-rendered to HTML for instant paint, then hydrated with tiny JS */}
193
- <Counter initialCount={10} step={2} />
194
- </Layout>
195
- );
196
- }
197
- ```
198
-
199
- ### What Happens Behind the Scenes:
200
- 1. **Server Phase**: The island is pre-rendered with `renderToString` and placed inside a container `<div data-island="Counter" data-props="...">`.
201
- 2. **Bundler Phase**: `esbuild` extracts and bundles only `CounterComponent` into `dist/assets/islands/Counter.js`.
202
- 3. **Injection Phase**: `<script type="module" src="/assets/islands/Counter.js"></script>` is injected right before `</body>`.
203
- 4. **Client Hydration**: The script queries `[data-island="Counter"]` and attaches listeners using React 19's `hydrateRoot`.
204
-
205
- ---
206
-
207
- ## 5. Hydration Directives
208
-
209
- Fine-tune client performance using the `client` prop:
210
-
211
- | Directive | Code Example | When It Hydrates |
212
- | :--- | :--- | :--- |
213
- | `load` *(default)* | `<Counter client="load" />` | Immediately upon `DOMContentLoaded`. Ideal for primary UI. |
214
- | `idle` | `<Counter client="idle" />` | During browser idle time via `requestIdleCallback`. Ideal for background widgets. |
215
- | `visible` | `<Counter client="visible" />` | When scrolled into view via `IntersectionObserver`. Ideal for below-the-fold content. |
216
-
217
- ---
218
-
219
- ## 6. CLI Commands & Options
220
-
221
- ```bash
222
- # Build static site to dist/
223
- react-html build
224
-
225
- # Customize directories
226
- react-html build --pages src/pages --out dist --root .
227
-
228
- # Start local preview server with auto-rebuild on file changes
229
- react-html dev --port 3000 --host localhost
230
-
231
- # Terminal UI documentation viewer
232
- react-html docs
233
- react-html docs --all
234
- react-html docs islands
235
- ```
236
-
237
- ---
238
-
239
- ## 7. Programmatic API Reference
240
-
241
- You can import and trigger `react-html` within custom build scripts or Node.js services:
242
-
243
- ```ts
244
- import { build, renderPageToHtml, createDevServer } from 'react-html';
245
-
246
- // 1. Run static build
247
- const result = await build({
248
- root: process.cwd(),
249
- pagesDir: 'src/pages',
250
- outDir: 'dist',
251
- minify: true
252
- });
253
- console.log(`Generated ${result.pages.length} pages.`);
254
-
255
- // 2. Programmatic dev server
256
- const server = await createDevServer({
257
- port: 8080,
258
- host: '127.0.0.1'
259
- });
260
- ```
261
-
262
- ---
263
-
264
- ## 8. Server App Integration (Express, Fastify, Node HTTP)
265
-
266
- You can import and use `react-html` directly inside your backend server application:
267
-
268
- ```ts
269
- import express from 'express';
270
- import { createEngine } from 'react-html';
271
- // or: import { createEngine } from 'react-html/server';
272
-
273
- const app = express();
274
-
275
- const engine = createEngine({
276
- pagesDir: 'src/pages',
277
- distDir: 'dist',
278
- // Automatically detects process.env.NODE_ENV === 'production'
279
- });
280
-
281
- // 1. Connect / Express Middleware
282
- // In Production: finds and serves pre-built static HTML from dist/
283
- // In Development: dynamically rebuilds TSX on the fly and returns the fresh HTML string!
284
- app.use(engine.middleware);
285
-
286
- // 2. Programmatic Route Rendering
287
- app.get('/dashboard', async (req, res) => {
288
- const html = await engine.render('dashboard', { user: req.user });
289
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
290
- res.send(html);
291
- });
292
-
293
- // 3. Native Node HTTP Server
294
- import http from 'node:http';
295
- const server = http.createServer(engine.handle);
296
- server.listen(3000);
297
- ```
298
-
299
- ### Environment Behavior:
300
- - **`process.env.NODE_ENV === 'production'`**:
301
- - Automatically searches for pre-built `.html` files in `distDir` (`dist/index.html`, `dist/about.html`).
302
- - Streams them immediately to clients with 0 rebuild overhead.
303
- - Serves static assets (`dist/assets/islands/*.js`, `dist/styles.css`).
304
- - **`process.env.NODE_ENV !== 'production'` (Development)**:
305
- - Dynamically rebuilds the corresponding `.tsx` page on each request.
306
- - Bundles any reactive islands on-the-fly.
307
- - Returns the fresh HTML string directly in the response—code edits in `src/pages` or `src/components` are reflected instantly on reload without server restarts!
308
-
309
- ---
310
-
311
- ## 9. Best Practices & Troubleshooting
312
-
313
- ### Preventing Hydration Mismatches (React Error #418)
314
- - **Do not read browser globals during initial render**: Avoid `window.innerWidth` or `localStorage.getItem()` directly in the component body. Read them inside a `useEffect()`.
315
- - **Props consistency**: Ensure initial props passed from the page match what the component expects.
316
- - **Protected formatting**: `react-html` automatically preserves the exact inner markup of islands so whitespace insertion does not invalidate React's DOM reconciliation.
317
-
@@ -1,184 +0,0 @@
1
- :root {
2
- --bg-color: #0f172a;
3
- --card-bg: #1e293b;
4
- --text-main: #f8fafc;
5
- --text-muted: #94a3b8;
6
- --primary: #38bdf8;
7
- --primary-hover: #0ea5e9;
8
- --border: #334155;
9
- --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10
- }
11
-
12
- * {
13
- box-sizing: border-box;
14
- margin: 0;
15
- padding: 0;
16
- }
17
-
18
- body {
19
- background-color: var(--bg-color);
20
- color: var(--text-main);
21
- font-family: var(--font);
22
- line-height: 1.6;
23
- min-height: 100vh;
24
- display: flex;
25
- flex-direction: column;
26
- }
27
-
28
- .container {
29
- max-width: 900px;
30
- margin: 0 auto;
31
- padding: 0 1.5rem;
32
- width: 100%;
33
- }
34
-
35
- header {
36
- border-bottom: 1px solid var(--border);
37
- padding: 1.25rem 0;
38
- background: rgba(15, 23, 42, 0.8);
39
- backdrop-filter: blur(8px);
40
- }
41
-
42
- header .header-inner {
43
- display: flex;
44
- justify-content: space-between;
45
- align-items: center;
46
- }
47
-
48
- .logo {
49
- font-size: 1.25rem;
50
- font-weight: 700;
51
- color: var(--primary);
52
- text-decoration: none;
53
- }
54
-
55
- nav a {
56
- color: var(--text-muted);
57
- text-decoration: none;
58
- margin-left: 1.5rem;
59
- transition: color 0.2s;
60
- }
61
-
62
- nav a:hover, nav a.active {
63
- color: var(--text-main);
64
- }
65
-
66
- main {
67
- flex: 1;
68
- padding: 3rem 0;
69
- }
70
-
71
- h1 {
72
- font-size: 2.25rem;
73
- margin-bottom: 1rem;
74
- }
75
-
76
- p.subtitle {
77
- color: var(--text-muted);
78
- font-size: 1.15rem;
79
- margin-bottom: 2rem;
80
- }
81
-
82
- .grid {
83
- display: grid;
84
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
85
- gap: 1.5rem;
86
- margin-bottom: 2.5rem;
87
- }
88
-
89
- .card {
90
- background: var(--card-bg);
91
- border: 1px solid var(--border);
92
- border-radius: 0.75rem;
93
- padding: 1.5rem;
94
- }
95
-
96
- .card h3 {
97
- margin-bottom: 0.5rem;
98
- color: var(--primary);
99
- }
100
-
101
- .card p {
102
- color: var(--text-muted);
103
- font-size: 0.95rem;
104
- }
105
-
106
- /* Island interactive counter styling */
107
- .counter-box {
108
- background: var(--card-bg);
109
- border: 1px solid var(--border);
110
- border-radius: 0.75rem;
111
- padding: 2rem;
112
- text-align: center;
113
- margin: 1.5rem 0;
114
- }
115
-
116
- .counter-value {
117
- font-size: 3rem;
118
- font-weight: 700;
119
- color: var(--primary);
120
- margin: 1rem 0;
121
- }
122
-
123
- .btn-group {
124
- display: flex;
125
- justify-content: center;
126
- gap: 1rem;
127
- }
128
-
129
- button {
130
- background: var(--primary);
131
- color: #0f172a;
132
- border: none;
133
- border-radius: 0.5rem;
134
- padding: 0.6rem 1.25rem;
135
- font-weight: 600;
136
- cursor: pointer;
137
- transition: background 0.2s;
138
- }
139
-
140
- button:hover {
141
- background: var(--primary-hover);
142
- }
143
-
144
- /* Tabs Island */
145
- .tabs-container {
146
- background: var(--card-bg);
147
- border: 1px solid var(--border);
148
- border-radius: 0.75rem;
149
- overflow: hidden;
150
- margin: 1.5rem 0;
151
- }
152
-
153
- .tabs-header {
154
- display: flex;
155
- background: #0b1120;
156
- border-bottom: 1px solid var(--border);
157
- }
158
-
159
- .tab-btn {
160
- background: transparent;
161
- color: var(--text-muted);
162
- padding: 0.75rem 1.5rem;
163
- border-radius: 0;
164
- border-bottom: 2px solid transparent;
165
- }
166
-
167
- .tab-btn.active {
168
- color: var(--primary);
169
- border-bottom: 2px solid var(--primary);
170
- background: rgba(56, 189, 248, 0.05);
171
- }
172
-
173
- .tab-content {
174
- padding: 1.5rem;
175
- }
176
-
177
- footer {
178
- border-top: 1px solid var(--border);
179
- padding: 2rem 0;
180
- text-align: center;
181
- color: var(--text-muted);
182
- font-size: 0.875rem;
183
- }
184
-
@@ -1,17 +0,0 @@
1
- import React, { ReactNode } from 'react';
2
-
3
- export interface CardProps {
4
- title: string;
5
- children: ReactNode;
6
- icon?: string;
7
- }
8
-
9
- export function Card({ title, children, icon }: CardProps) {
10
- return (
11
- <div className="card">
12
- <h3>{icon ? `${icon} ` : ''}{title}</h3>
13
- <div>{children}</div>
14
- </div>
15
- );
16
- }
17
-
@@ -1,35 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { island } from '../../../src/index.js';
3
-
4
- export interface CounterProps {
5
- initialCount?: number;
6
- step?: number;
7
- }
8
-
9
- function CounterComponent({ initialCount = 0, step = 1 }: CounterProps) {
10
- const [count, setCount] = useState(initialCount);
11
-
12
- return (
13
- <div className="counter-box">
14
- <h3>⚡ Interactive Counter Island</h3>
15
- <p style={{ color: '#94a3b8', fontSize: '0.9rem' }}>
16
- This component is rendered statically on the server, then hydrated with tiny JavaScript.
17
- </p>
18
- <div className="counter-value">{count}</div>
19
- <div className="btn-group">
20
- <button type="button" onClick={() => setCount((c) => c - step)}>
21
- - {step}
22
- </button>
23
- <button type="button" onClick={() => setCount(0)}>
24
- Reset
25
- </button>
26
- <button type="button" onClick={() => setCount((c) => c + step)}>
27
- + {step}
28
- </button>
29
- </div>
30
- </div>
31
- );
32
- }
33
-
34
- export const Counter = island(CounterComponent, import.meta.url, 'Counter');
35
- export default Counter;
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
-
3
- export function Footer() {
4
- const currentYear = new Date().getFullYear();
5
- return (
6
- <footer>
7
- <div className="container">
8
- <p>&copy; {currentYear} react-html. Modular pure HTML engine with selective reactive islands.</p>
9
- </div>
10
- </footer>
11
- );
12
- }
13
-
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
-
3
- export interface HeaderProps {
4
- currentPath?: string;
5
- }
6
-
7
- export function Header({ currentPath = '/' }: HeaderProps) {
8
- return (
9
- <header>
10
- <div className="container header-inner">
11
- <a href="/" className="logo">⚛️ react-html</a>
12
- <nav>
13
- <a href="/" className={currentPath === '/' ? 'active' : ''}>Home (Interactive)</a>
14
- <a href="/pure.html" className={currentPath === '/pure' ? 'active' : ''}>Pure HTML (0 KB JS)</a>
15
- </nav>
16
- </div>
17
- </header>
18
- );
19
- }
20
-
@@ -1,39 +0,0 @@
1
- import React, { ReactNode } from 'react';
2
- import { Header } from './Header.js';
3
- import { Footer } from './Footer.js';
4
-
5
- export interface LayoutProps {
6
- title?: string;
7
- description?: string;
8
- currentPath?: string;
9
- children: ReactNode;
10
- }
11
-
12
- export function Layout({
13
- title = 'react-html Demo',
14
- description = 'Modular pure HTML generated with React',
15
- currentPath = '/',
16
- children
17
- }: LayoutProps) {
18
- return (
19
- <html lang="en">
20
- <head>
21
- <meta charSet="UTF-8" />
22
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
23
- <title>{title}</title>
24
- <meta name="description" content={description} />
25
- <link rel="stylesheet" href="/styles.css" />
26
- </head>
27
- <body>
28
- <Header currentPath={currentPath} />
29
- <main>
30
- <div className="container">
31
- {children}
32
- </div>
33
- </main>
34
- <Footer />
35
- </body>
36
- </html>
37
- );
38
- }
39
-
@@ -1,59 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { island } from '../../../src/index.js';
3
-
4
- export interface TabItem {
5
- id: string;
6
- label: string;
7
- content: string;
8
- }
9
-
10
- export interface TabsProps {
11
- tabs?: TabItem[];
12
- defaultTab?: string;
13
- }
14
-
15
- const defaultTabs: TabItem[] = [
16
- {
17
- id: 'features',
18
- label: '✨ Features',
19
- content: 'Write modular React components with standard JSX. Reusable layouts, cards, buttons, with 0 repetitive boilerplate.'
20
- },
21
- {
22
- id: 'islands',
23
- label: '🏝️ Islands',
24
- content: 'Only reactive components compile to client-side JavaScript. Everything else is rendered to pure, clean, zero-JS HTML.'
25
- },
26
- {
27
- id: 'speed',
28
- label: '⚡ Speed',
29
- content: 'Ultra-fast builds powered by esbuild. Instant page loads for users because the browser only receives semantic HTML.'
30
- }
31
- ];
32
-
33
- function TabsComponent({ tabs = defaultTabs, defaultTab = 'features' }: TabsProps) {
34
- const [activeTab, setActiveTab] = useState(defaultTab);
35
- const currentTab = tabs.find((t) => t.id === activeTab) || tabs[0];
36
-
37
- return (
38
- <div className="tabs-container">
39
- <div className="tabs-header">
40
- {tabs.map((tab) => (
41
- <button
42
- key={tab.id}
43
- type="button"
44
- className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`}
45
- onClick={() => setActiveTab(tab.id)}
46
- >
47
- {tab.label}
48
- </button>
49
- ))}
50
- </div>
51
- <div className="tab-content">
52
- <p>{currentTab.content}</p>
53
- </div>
54
- </div>
55
- );
56
- }
57
-
58
- export const Tabs = island(TabsComponent, import.meta.url, 'Tabs');
59
- export default Tabs;