cms-renderer 0.6.4 → 0.6.5

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 CHANGED
@@ -1,385 +1,295 @@
1
- # Website
1
+ # cms-renderer
2
2
 
3
- **CMS-powered website built with Next.js 15 and App Router**
3
+ `cms-renderer` is a library for rendering CMS-powered routes inside a Next.js app.
4
4
 
5
- ```
6
- CMS (authors content) --> tRPC API --> Website (renders blocks)
7
- ```
8
-
9
- The website app fetches structured page data via tRPC and renders blocks using the ComponentMap pattern. Article content uses WASM-powered markdown rendering for performance.
10
-
11
- ---
12
-
13
- ## Quick Start
5
+ It provides:
14
6
 
15
- **Prerequisites**
7
+ - A catch-all route component that fetches CMS routes and blocks from the CMS API and renders them with your React component registry
8
+ - A proxy helper for mounting the CMS admin and API behind your app
9
+ - Schema-fetching utilities for headless document access
10
+ - Docs-oriented markdown rendering and styles
11
+ - Type exports for block registries and route params
16
12
 
17
- | Tool | Version | Check Command |
18
- |------|---------|---------------|
19
- | Bun | 1.2.8+ | `bun --version` |
20
- | Node.js | 18+ | `node --version` |
13
+ It is meant to be embedded in an existing Next.js app. It is not a standalone website.
21
14
 
22
- **Installation**
15
+ ## Install
23
16
 
24
17
  ```bash
25
- # From monorepo root
26
- bun install
27
-
28
- # Start the website dev server
29
- cd apps/renderer
30
- bun run dev
18
+ npm install cms-renderer
31
19
  ```
32
20
 
33
- **Verification**
21
+ This package is intended for React + Next.js applications that can reach a running CMS deployment.
34
22
 
35
- Open [http://localhost:3001](http://localhost:3001). You should see:
23
+ ## Exported Entry Points
36
24
 
37
- ```
38
- Website
39
- CMS-powered website built with Next.js 15 and App Router.
40
- ```
25
+ Main entry points:
41
26
 
42
- Visit [http://localhost:3001/demo](http://localhost:3001/demo) to see the complete demo page with navigation, header, and article blocks.
27
+ - `cms-renderer/lib/renderer` for catch-all route rendering
28
+ - `cms-renderer/lib/types` for block and route types
29
+ - `cms-renderer/lib/proxy` for proxying CMS admin and API traffic
30
+ - `cms-renderer/lib/schema` for headless schema-based reads
31
+ - `cms-renderer/lib/custom-schemas` for fetching custom schema metadata and generating Zod code
32
+ - `cms-renderer/lib/docs-markdown` and `cms-renderer/styles/docs-markdown.css` for docs markdown rendering
33
+ - `cms-renderer/lib/refresher` for client-side refresh on CMS updates
43
34
 
44
- ---
35
+ Additional utility exports:
45
36
 
46
- ## Architecture
37
+ - `cms-renderer/lib/block-renderer`
38
+ - `cms-renderer/lib/block-toolbar`
39
+ - `cms-renderer/lib/client-editable-block`
40
+ - `cms-renderer/lib/cms-api`
41
+ - `cms-renderer/lib/markdown-utils`
42
+ - `cms-renderer/lib/result`
43
+ - `cms-renderer/lib/trpc`
44
+ - `cms-renderer/lib/image/lazy-load`
47
45
 
48
- ### How This App Fits in the Monorepo
46
+ ## Core Usage
49
47
 
50
- ```
51
- auteur/
52
- ├── apps/
53
- │ ├── cms/ # Content authoring (Lexical editor)
54
- │ └── renderer/ # This app - renders CMS content
55
- └── packages/
56
- ├── cms-schema/ # Shared Zod schemas
57
- └── markdown-wasm/ # WASM markdown renderer
58
- ```
48
+ ### 1. Render CMS Routes in a Catch-All Next.js Page
59
49
 
60
- The website consumes schemas from `@repo/cms-schema` and uses `@repo/markdown-wasm` for article body rendering. It does not import from `apps/cms/` directly.
50
+ Use the default export from `cms-renderer/lib/renderer` inside a catch-all route such as `app/[...slug]/page.tsx`.
61
51
 
62
- ### Key Directories
52
+ ```tsx
53
+ import ParametricRoutePage from 'cms-renderer/lib/renderer';
54
+ import type { BlockComponentRegistry } from 'cms-renderer/lib/types';
55
+ import HeaderBlock from '@/components/HeaderBlock';
63
56
 
64
- ```
65
- apps/renderer/
66
- ├── app/ # Next.js App Router pages
67
- │ ├── [slug]/ # Dynamic page routes
68
- │ │ ├── page.tsx # Server Component fetches via tRPC
69
- │ │ ├── loading.tsx # Skeleton loading state
70
- │ │ └── error.tsx # Error boundary
71
- │ ├── api/trpc/[trpc]/ # tRPC API handler
72
- │ ├── layout.tsx # Root layout with CSS imports
73
- │ ├── globals.css # CSS reset
74
- │ └── page.tsx # Homepage
75
- ├── components/
76
- │ ├── blocks/ # Block components + registry
77
- │ │ ├── navigation-block.tsx
78
- │ │ ├── header-block.tsx
79
- │ │ ├── article-block.tsx
80
- │ │ ├── block-renderer.tsx # Dispatcher component
81
- │ │ ├── types.ts # BlockData discriminated union
82
- │ │ └── index.ts # Exports + blockComponents registry
83
- │ ├── page-renderer.tsx # Renders array of blocks
84
- │ └── trpc-provider.tsx # Client-side tRPC/React Query
85
- ├── server/
86
- │ ├── trpc.ts # tRPC initialization
87
- │ └── routers/
88
- │ ├── blocks/ # Block data procedures
89
- │ │ ├── get-navigation.ts
90
- │ │ ├── get-header.ts
91
- │ │ └── get-article.ts
92
- │ └── pages/ # Page composition procedures
93
- │ └── get-page.ts
94
- ├── seed/ # Mock data for development
95
- │ └── index.ts
96
- ├── styles/
97
- │ └── blocks.css # Vanilla CSS for blocks
98
- └── lib/
99
- └── trpc.ts # Client-side tRPC hooks
100
- ```
57
+ const registry: Partial<BlockComponentRegistry> = {
58
+ header: HeaderBlock,
59
+ };
60
+
61
+ interface PageProps {
62
+ params: Promise<{ slug: string[] }>;
63
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
64
+ }
101
65
 
102
- ### Data Flow
66
+ export default async function Page({ params, searchParams }: PageProps) {
67
+ const { slug } = await params;
103
68
 
104
- ```
105
- ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
106
- │ Dynamic Route │────>│ tRPC Caller │────>│ Mock Data
107
- │ [slug]/page │ │ pages.getPage │ │ seed/index.ts │
108
- └─────────────────┘ └─────────────────┘ └─────────────────┘
109
-
110
- v
111
- ┌─────────────────┐ ┌─────────────────┐
112
- │ PageRenderer │────>│ BlockRenderer │
113
- (iterates) │ │ (dispatches) │
114
- └─────────────────┘ └─────────────────┘
115
-
116
- ┌──────────────────────┼──────────────────────┐
117
- v v v
118
- ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
119
- │ NavigationBlock│ │ HeaderBlock │ │ ArticleBlock │
120
- │ │ │ │ │ (WASM markdown)│
121
- └───────────────┘ └───────────────┘ └───────────────┘
69
+ return (
70
+ <ParametricRoutePage
71
+ registry={registry}
72
+ cmsUrl={process.env.NEXT_PUBLIC_CMS_URL!}
73
+ apiKey={process.env.CMS_API_KEY}
74
+ websiteId={process.env.NEXT_PUBLIC_WEBSITE_ID}
75
+ params={Promise.resolve({ slug })}
76
+ searchParams={searchParams}
77
+ />
78
+ );
79
+ }
122
80
  ```
123
81
 
124
- ---
82
+ What the route component does:
125
83
 
126
- ## Development Guide
84
+ - Resolves the incoming path from the catch-all slug
85
+ - Fetches the route definition from the CMS API
86
+ - Fetches each referenced block
87
+ - Maps each block to your registry by `schema_name`
88
+ - Renders built-in and custom block types
89
+ - Supports `edit_mode` and `ai_preview` query params
127
90
 
128
- ### Available Scripts
91
+ ### 2. Register Block Components
129
92
 
130
- | Script | Command | Description |
131
- |--------|---------|-------------|
132
- | `dev` | `bun run dev` | Start dev server on port 3001 |
133
- | `build` | `bun run build` | Production build |
134
- | `start` | `bun run start` | Start production server |
135
- | `lint` | `bun run lint` | Run Biome linter |
136
- | `check-types` | `bun run check-types` | TypeScript type checking |
93
+ Block components receive `content` and optional `routeParams`.
137
94
 
138
- ### Hot Reload Behavior
95
+ ```tsx
96
+ import type { BlockComponentProps } from 'cms-renderer/lib/types';
97
+
98
+ type HeroProps = BlockComponentProps<{
99
+ headline: string;
100
+ subheadline?: string;
101
+ }>;
102
+
103
+ export default function HeroBlock({ content }: HeroProps) {
104
+ return (
105
+ <section>
106
+ <h1>{content.headline}</h1>
107
+ {content.subheadline ? <p>{content.subheadline}</p> : null}
108
+ </section>
109
+ );
110
+ }
111
+ ```
139
112
 
140
- - **Page changes**: Instant hot reload
141
- - **tRPC procedures**: Requires server restart
142
- - **CSS changes**: Instant hot reload
143
- - **Block components**: Instant hot reload
113
+ Useful types are exported from `cms-renderer/lib/types`:
144
114
 
145
- ### Environment Variables
115
+ - `BlockData`
116
+ - `BlockComponentProps<T>`
117
+ - `BlockComponentRegistry`
118
+ - `ResolvedRouteParams`
146
119
 
147
- No environment variables are required for development. The app uses mock data from `seed/index.ts`.
120
+ ### 3. Configure Environment
148
121
 
149
- For production with a real database, you would add:
122
+ Required for route rendering:
150
123
 
151
124
  ```env
152
- # .env.local (not required for development)
153
- DATABASE_URL=your-database-url
125
+ NEXT_PUBLIC_CMS_URL=https://cms.example.com
126
+ NEXT_PUBLIC_WEBSITE_ID=00000000-0000-0000-0000-000000000000
154
127
  ```
155
128
 
156
- ---
157
-
158
- ## Key Concepts
159
-
160
- ### ComponentMap Pattern
161
-
162
- The website uses a registry-based pattern to map block types to React components:
129
+ Optional:
163
130
 
164
- ```typescript
165
- // components/blocks/index.ts
166
- export const blockComponents: BlockComponentRegistry = {
167
- navigation: NavigationBlock,
168
- header: HeaderBlock,
169
- article: ArticleBlock,
170
- } as const;
131
+ ```env
132
+ CMS_API_KEY=your-api-key
133
+ ADMIN_UPSTREAM_ORIGIN=https://cms.example.com
134
+ NEXT_PUBLIC_CMS_API_URL=https://cms.example.com
171
135
  ```
172
136
 
173
- The `BlockRenderer` uses this registry to dispatch blocks:
137
+ `ParametricRoutePage` requires:
174
138
 
175
- ```tsx
176
- // Render any block by type
177
- <BlockRenderer block={{ type: 'header', content: {...} }} />
139
+ - `cmsUrl`, passed as a prop
140
+ - a website ID, passed as a prop or available through environment variables
178
141
 
179
- // Render a page of blocks
180
- {page.blocks.map((block, i) => (
181
- <BlockRenderer key={i} block={block} />
182
- ))}
183
- ```
142
+ `websiteId` can be passed explicitly to `ParametricRoutePage`, or it can be read from:
184
143
 
185
- ### Block Types
144
+ - `NEXT_PUBLIC_WEBSITE_ID`
145
+ - `WEBSITE_ID`
146
+ - `CMS_WEBSITE_ID`
186
147
 
187
- | Block | Component | Content Fields |
188
- |-------|-----------|----------------|
189
- | `navigation` | `NavigationBlock` | logo, ariaLabel, links (3-level hierarchy) |
190
- | `header` | `HeaderBlock` | headline, subheadline, backgroundImage, ctaButton, alignment |
191
- | `article` | `ArticleBlock` | headline, author, publishedAt, body (markdown), tags |
148
+ Use `CMS_API_KEY` when your CMS requires authenticated access.
192
149
 
193
- ### tRPC Integration
150
+ `NEXT_PUBLIC_CMS_API_URL` is only used by the default `schema` export from `cms-renderer/lib/schema`. If you call `configureSchema(...)` directly, pass `cmsUrl` explicitly instead.
194
151
 
195
- **Server Component** (recommended):
152
+ If no website ID is available, `ParametricRoutePage` throws at runtime.
196
153
 
197
- ```tsx
198
- // Direct procedure call in Server Components
199
- import { createCaller } from '@/server/routers';
200
- import { createContext } from '@/server/trpc';
154
+ ## CMS Proxy
201
155
 
202
- export default async function Page({ params }: PageProps) {
203
- const { slug } = await params;
204
- const caller = createCaller(createContext());
205
- const page = await caller.pages.getPage({ slug });
156
+ Use `createCmsProxy` when your app needs to proxy `/admin`, `/api`, `/auth`, and related CMS assets to an upstream CMS deployment.
157
+ Use it from `proxy.ts` or `middleware.ts`, depending on how your Next.js app is structured.
206
158
 
207
- return <PageRenderer title={page.title} blocks={page.blocks} />;
208
- }
209
- ```
159
+ ```ts
160
+ import { createCmsProxy, cmsProxyMatcher } from 'cms-renderer/lib/proxy';
161
+ import type { NextRequest } from 'next/server';
210
162
 
211
- **Client Component** (when needed):
163
+ const cmsProxy = createCmsProxy({
164
+ upstream: process.env.ADMIN_UPSTREAM_ORIGIN,
165
+ });
212
166
 
213
- ```tsx
214
- 'use client';
215
- import { trpc } from '@/lib/trpc';
216
-
217
- function PageClient({ slug }: { slug: string }) {
218
- const { data, isLoading } = trpc.pages.getPage.useQuery({ slug });
219
-
220
- if (isLoading) return <div>Loading...</div>;
221
- return <PageRenderer title={data.title} blocks={data.blocks} />;
167
+ export async function middleware(request: NextRequest) {
168
+ return cmsProxy(request);
222
169
  }
223
- ```
224
-
225
- ### Edit-Mode Block Toolbar
226
170
 
227
- When a page is rendered inside the CMS iframe, hovering a block shows a floating toolbar with move-up / move-down / add / delete controls.
228
-
229
- **Viewport-aware positioning**
230
-
231
- On `mouseenter`, the block's `DOMRect` is captured and passed to `BlockToolbar`. After the toolbar renders, a `useLayoutEffect` measures the toolbar's actual dimensions and tries up to five candidate placements in order, stopping at the first one that fits fully inside the viewport:
232
-
233
- | # | Placement |
234
- |---|-----------|
235
- | 1 | Below block, centered |
236
- | 2 | Above block, centered |
237
- | 3 | Below block, right-aligned |
238
- | 4 | Below block, left-aligned |
239
- | 5 | Above block, right-aligned |
171
+ export const config = {
172
+ matcher: cmsProxyMatcher,
173
+ };
174
+ ```
240
175
 
241
- If none of the five candidates fit (e.g. the block is larger than the viewport), the toolbar falls back to candidate 1 clamped to the viewport edges. The toolbar starts with `visibility: hidden` and is made visible only after positioning, so there is no positional flash.
176
+ The proxy helper:
242
177
 
243
- **Components involved**
178
+ - Forwards `/admin`, `/api`, and `/auth`
179
+ - Can proxy additional custom path prefixes
180
+ - Rewrites redirects back to the current host when appropriate
181
+ - Rewrites cookies for the current deployment host
182
+ - Proxies admin-originated static asset requests
244
183
 
245
- - `lib/client-editable-block.tsx` captures the block rect on hover and portals `BlockToolbar` to `document.body`
246
- - `lib/block-toolbar.tsx` — runs the candidate loop in `useLayoutEffect` and applies `top`/`left` directly on the element
184
+ ## Headless Schema Access
247
185
 
248
- ### WASM Markdown Rendering
186
+ Use `configureSchema` or the default `schema` export when you want published documents for a custom schema outside the block renderer.
249
187
 
250
- The `ArticleBlock` uses `@repo/markdown-wasm` for fast markdown-to-React conversion:
188
+ ```ts
189
+ import { configureSchema } from 'cms-renderer/lib/schema';
251
190
 
252
- ```tsx
253
- import { MarkdownRenderer } from '@repo/markdown-wasm';
191
+ const cms = configureSchema({
192
+ cmsUrl: process.env.NEXT_PUBLIC_CMS_URL!,
193
+ apiKey: process.env.CMS_API_KEY,
194
+ websiteId: process.env.NEXT_PUBLIC_WEBSITE_ID,
195
+ });
254
196
 
255
- // Inside ArticleBlock
256
- <MarkdownRenderer content={body} className="article-block__content" />
197
+ const posts = await cms.name('post').fetchAll();
198
+ const post = await cms.name('post').fetchSingleById('document-id');
199
+ const frenchPosts = await cms.name('post').translation('fr').fetchAll();
257
200
  ```
258
201
 
259
- md4w parses markdown 2.5x faster than JavaScript alternatives and outputs a traversable AST that maps directly to React components.
202
+ Available methods:
260
203
 
261
- ---
204
+ - `fetchAll()`
205
+ - `fetchSingle()`
206
+ - `fetchSingleById(id)`
207
+ - `translation(language)`
262
208
 
263
- ## API Routes Reference
209
+ If you use the default `schema` export, it reads `NEXT_PUBLIC_CMS_API_URL` from the environment. If your app already has a CMS base URL under a different variable name, prefer `configureSchema(...)` and pass `cmsUrl` explicitly.
264
210
 
265
- ### tRPC Endpoints
211
+ ## Custom Schema Code Generation
266
212
 
267
- | Endpoint | Method | Input | Returns |
268
- |----------|--------|-------|---------|
269
- | `blocks.getNavigation` | Query | None | Navigation data |
270
- | `blocks.getHeader` | Query | None | Header data |
271
- | `blocks.getArticle` | Query | None | Article data |
272
- | `pages.getPage` | Query | `{ slug: string }` | Page with blocks array |
213
+ Use `cms-renderer/lib/custom-schemas` to fetch custom schema metadata and generate typed Zod schema code for your app.
273
214
 
274
- ### Testing Endpoints
215
+ ```ts
216
+ import {
217
+ fetchAllCustomSchemaFields,
218
+ saveZodSchemaCode,
219
+ } from 'cms-renderer/lib/custom-schemas';
275
220
 
276
- ```bash
277
- # Start the dev server
278
- bun run dev
221
+ const schemas = await fetchAllCustomSchemaFields({
222
+ cmsUrl: process.env.NEXT_PUBLIC_CMS_URL!,
223
+ apiKey: process.env.CMS_API_KEY,
224
+ websiteId: process.env.NEXT_PUBLIC_WEBSITE_ID!,
225
+ });
279
226
 
280
- # Test endpoints (in another terminal)
281
- curl "http://localhost:3001/api/trpc/blocks.getNavigation"
282
- curl "http://localhost:3001/api/trpc/blocks.getHeader"
283
- curl "http://localhost:3001/api/trpc/pages.getPage?input=%7B%22json%22%3A%7B%22slug%22%3A%22demo%22%7D%7D"
227
+ await saveZodSchemaCode(schemas, 'generated/cms-schemas.ts');
284
228
  ```
285
229
 
286
- ---
230
+ Also exported:
287
231
 
288
- ## Testing
232
+ - `fetchCustomSchemaFields(options, schemaName)`
233
+ - `buildZodSchemas(schemas)`
289
234
 
290
- The website app does not currently have unit tests. Integration testing is done via the tRPC endpoints.
235
+ ## Docs Markdown
291
236
 
292
- ```bash
293
- # Type check
294
- bun run check-types
237
+ `cms-renderer` includes a docs-oriented markdown boundary for starter apps and docs sites.
295
238
 
296
- # Lint
297
- bun run lint
298
- ```
299
-
300
- Test files would live at:
301
- - `components/blocks/*.test.tsx` for block components
302
- - `server/routers/**/*.test.ts` for tRPC procedures
303
-
304
- ---
305
-
306
- ## Troubleshooting
307
-
308
- ### Port 3001 Already in Use
309
-
310
- ```bash
311
- # Find and kill the process
312
- lsof -i :3001
313
- kill -9 <PID>
239
+ ```tsx
240
+ import { DocsMarkdown } from 'cms-renderer/lib/docs-markdown';
241
+ import 'cms-renderer/styles/docs-markdown.css';
314
242
 
315
- # Or use a different port
316
- bun run dev -- --port 3002
243
+ export default async function Page() {
244
+ return <DocsMarkdown content={'# Hello\n\nThis came from the CMS.'} />;
245
+ }
317
246
  ```
318
247
 
319
- ### Cannot Find Module '@repo/cms-schema'
320
-
321
- ```bash
322
- # Run bun install from monorepo root
323
- cd ../..
324
- bun install
325
- ```
248
+ `DocsMarkdown` supports:
326
249
 
327
- ### TypeScript Errors on First Run
250
+ - WASM-backed markdown parsing
251
+ - Syntax-highlighted code blocks
252
+ - Optional custom image rendering via `renderImage`
253
+ - Default styling through `styles/docs-markdown.css`
328
254
 
329
- The `.next/types` directory is generated on first dev server run:
255
+ ## Live Refresh
330
256
 
331
- ```bash
332
- bun run dev # Run once to generate types
333
- bun run check-types # Now type checking works
334
- ```
257
+ Use `Refresher` in client components to refresh the current route when CMS content changes.
258
+ You can also provide `onInvalidate` to run app-specific invalidation before `router.refresh()`.
335
259
 
336
- ### WASM Module Not Found
260
+ ```tsx
261
+ 'use client';
337
262
 
338
- If you see errors about md4w:
263
+ import { Refresher } from 'cms-renderer/lib/refresher';
339
264
 
340
- ```bash
341
- # Rebuild the markdown-wasm package
342
- cd ../../packages/markdown-wasm
343
- bun install
344
- bun run build
265
+ export function CmsLiveRefresh() {
266
+ return (
267
+ <Refresher
268
+ cmsUrl={process.env.NEXT_PUBLIC_CMS_URL!}
269
+ websiteId={process.env.NEXT_PUBLIC_WEBSITE_ID!}
270
+ apiKey={process.env.NEXT_PUBLIC_CMS_API_KEY}
271
+ />
272
+ );
273
+ }
345
274
  ```
346
275
 
347
- ### tRPC 'NOT_FOUND' Error
348
-
349
- The mock data only includes pages with slugs: `demo`, `about`, `blog`. Requesting other slugs returns a 404.
276
+ ## Edit Mode
350
277
 
351
- ---
278
+ When the page is rendered with `edit_mode=true` or `edit_mode=1`, the renderer enables editable wrappers and block toolbar behavior for CMS iframe editing.
352
279
 
353
- ## Related Documentation
280
+ In normal public traffic, you do not need to do anything special for edit mode.
281
+ This mode is intended for CMS-integrated editing flows, not for public pages.
354
282
 
355
- - **Tutorial**: [`_docs/cms-website-integration/`](../../_docs/cms-website-integration/00-overview.md) - Full 11-chapter tutorial
356
- - **CMS App**: [`apps/cms/README.md`](../cms/README.md) - Content authoring application
357
- - **Schema Package**: [`packages/cms-schema/`](../../packages/cms-schema/) - Shared Zod schemas
358
- - **Markdown Package**: [`packages/markdown-wasm/`](../../packages/markdown-wasm/) - WASM renderer
283
+ ## Contributor Notes
359
284
 
360
- ---
361
-
362
- ## Contributing
363
-
364
- ### Code Style
365
-
366
- This project uses [Biome](https://biomejs.dev/) for linting and formatting:
285
+ Useful package scripts:
367
286
 
368
287
  ```bash
369
- bun run lint # Check for issues
370
- bun run lint --fix # Auto-fix issues
288
+ bun run build
289
+ bun run check-types
290
+ bun run lint
291
+ bun run test
292
+ bun run test:coverage
371
293
  ```
372
294
 
373
- ### Adding a New Block Type
374
-
375
- 1. Define the schema in `apps/cms/app/schemas/`
376
- 2. Add mock data to `seed/index.ts`
377
- 3. Create the component in `components/blocks/`
378
- 4. Add to `blockComponents` registry in `components/blocks/index.ts`
379
- 5. Add the type to `BlockData` union in `components/blocks/types.ts`
380
-
381
- ### Pull Requests
382
-
383
- - Run `bun run check-types` before submitting
384
- - Run `bun run lint` to ensure code style compliance
385
- - Test the demo page at `/demo` renders correctly
295
+ `dist/` is generated by `tsup` during build and publish.
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
 
4
4
  // lib/block-toolbar.tsx
5
- import { ChevronDown, ChevronUp, Plus, Trash2 } from "lucide-react";
5
+ import { ChevronDownIcon, ChevronUpIcon, PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
6
6
  import { forwardRef } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
@@ -23,7 +23,7 @@ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
23
23
  e.stopPropagation();
24
24
  handleAction("move-up");
25
25
  },
26
- children: /* @__PURE__ */ jsx(ChevronUp, {})
26
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, {})
27
27
  }
28
28
  ),
29
29
  /* @__PURE__ */ jsx(
@@ -37,7 +37,7 @@ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
37
37
  e.stopPropagation();
38
38
  handleAction("move-down");
39
39
  },
40
- children: /* @__PURE__ */ jsx(ChevronDown, {})
40
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
41
41
  }
42
42
  ),
43
43
  /* @__PURE__ */ jsx(
@@ -51,7 +51,7 @@ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
51
51
  e.stopPropagation();
52
52
  handleAction("add-block");
53
53
  },
54
- children: /* @__PURE__ */ jsx(Plus, {})
54
+ children: /* @__PURE__ */ jsx(PlusIcon, {})
55
55
  }
56
56
  ),
57
57
  /* @__PURE__ */ jsx(
@@ -66,7 +66,7 @@ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
66
66
  e.stopPropagation();
67
67
  handleAction("delete");
68
68
  },
69
- children: /* @__PURE__ */ jsx(Trash2, {})
69
+ children: /* @__PURE__ */ jsx(TrashIcon, {})
70
70
  }
71
71
  )
72
72
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../lib/block-toolbar.tsx"],"sourcesContent":["'use client';\n\nimport { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react';\nimport { forwardRef } from 'react';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * Position is managed imperatively by the parent via a forwarded ref —\n * the toolbar follows the mouse cursor and has no internal layout logic.\n */\n\nexport const BlockToolbar = forwardRef<HTMLDivElement, { blockId: string }>(function BlockToolbar(\n { blockId },\n ref\n) {\n const handleAction = (action: string) => {\n if (typeof window !== 'undefined' && window.parent && window.parent !== window) {\n window.parent.postMessage({ type: 'cms-block-action', action, blockId }, '*');\n }\n };\n\n return (\n <div ref={ref} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\">\n <button\n type=\"button\"\n title=\"Move up\"\n aria-label=\"Move up\"\n data-action=\"move-up\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-up');\n }}\n >\n <ChevronUp />\n </button>\n <button\n type=\"button\"\n title=\"Move down\"\n aria-label=\"Move down\"\n data-action=\"move-down\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-down');\n }}\n >\n <ChevronDown />\n </button>\n <button\n type=\"button\"\n title=\"Add block\"\n aria-label=\"Add block\"\n data-action=\"add-block\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block');\n }}\n >\n <Plus />\n </button>\n <button\n type=\"button\"\n className=\"delete\"\n title=\"Delete block\"\n aria-label=\"Delete block\"\n data-action=\"delete\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('delete');\n }}\n >\n <Trash2 />\n </button>\n </div>\n );\n});\n"],"mappings":";;;;AAEA,SAAS,aAAa,WAAW,MAAM,cAAc;AACrD,SAAS,kBAAkB;AAqBvB,SAWI,KAXJ;AAXG,IAAM,eAAe,WAAgD,SAASA,cACnF,EAAE,QAAQ,GACV,KACA;AACA,QAAM,eAAe,CAAC,WAAmB;AACvC,QAAI,OAAO,WAAW,eAAe,OAAO,UAAU,OAAO,WAAW,QAAQ;AAC9E,aAAO,OAAO,YAAY,EAAE,MAAM,oBAAoB,QAAQ,QAAQ,GAAG,GAAG;AAAA,IAC9E;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,KAAU,WAAU,qBAAoB,oBAAiB,QAC5D;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,SAAS;AAAA,QACxB;AAAA,QAEA,8BAAC,aAAU;AAAA;AAAA,IACb;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,eAAY;AAAA;AAAA,IACf;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,QAAK;AAAA;AAAA,IACR;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,QAAQ;AAAA,QACvB;AAAA,QAEA,8BAAC,UAAO;AAAA;AAAA,IACV;AAAA,KACF;AAEJ,CAAC;","names":["BlockToolbar"]}
1
+ {"version":3,"sources":["../../lib/block-toolbar.tsx"],"sourcesContent":["'use client';\n\nimport { ChevronDownIcon, ChevronUpIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';\nimport { forwardRef } from 'react';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * Position is managed imperatively by the parent via a forwarded ref —\n * the toolbar follows the mouse cursor and has no internal layout logic.\n */\n\nexport const BlockToolbar = forwardRef<HTMLDivElement, { blockId: string }>(function BlockToolbar(\n { blockId },\n ref\n) {\n const handleAction = (action: string) => {\n if (typeof window !== 'undefined' && window.parent && window.parent !== window) {\n window.parent.postMessage({ type: 'cms-block-action', action, blockId }, '*');\n }\n };\n\n return (\n <div ref={ref} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\">\n <button\n type=\"button\"\n title=\"Move up\"\n aria-label=\"Move up\"\n data-action=\"move-up\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-up');\n }}\n >\n <ChevronUpIcon />\n </button>\n <button\n type=\"button\"\n title=\"Move down\"\n aria-label=\"Move down\"\n data-action=\"move-down\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-down');\n }}\n >\n <ChevronDownIcon />\n </button>\n <button\n type=\"button\"\n title=\"Add block\"\n aria-label=\"Add block\"\n data-action=\"add-block\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block');\n }}\n >\n <PlusIcon />\n </button>\n <button\n type=\"button\"\n className=\"delete\"\n title=\"Delete block\"\n aria-label=\"Delete block\"\n data-action=\"delete\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('delete');\n }}\n >\n <TrashIcon />\n </button>\n </div>\n );\n});\n"],"mappings":";;;;AAEA,SAAS,iBAAiB,eAAe,UAAU,iBAAiB;AACpE,SAAS,kBAAkB;AAqBvB,SAWI,KAXJ;AAXG,IAAM,eAAe,WAAgD,SAASA,cACnF,EAAE,QAAQ,GACV,KACA;AACA,QAAM,eAAe,CAAC,WAAmB;AACvC,QAAI,OAAO,WAAW,eAAe,OAAO,UAAU,OAAO,WAAW,QAAQ;AAC9E,aAAO,OAAO,YAAY,EAAE,MAAM,oBAAoB,QAAQ,QAAQ,GAAG,GAAG;AAAA,IAC9E;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,KAAU,WAAU,qBAAoB,oBAAiB,QAC5D;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,SAAS;AAAA,QACxB;AAAA,QAEA,8BAAC,iBAAc;AAAA;AAAA,IACjB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,mBAAgB;AAAA;AAAA,IACnB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,YAAS;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,QAAQ;AAAA,QACvB;AAAA,QAEA,8BAAC,aAAU;AAAA;AAAA,IACb;AAAA,KACF;AAEJ,CAAC;","names":["BlockToolbar"]}