@bettercms-ai/ui 0.3.0 → 0.4.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.
- package/dist/{Video-Dd3TLlXA.d.ts → Video-1CG9bFXZ.d.ts} +4 -2
- package/dist/blocks.d.ts +3 -3
- package/dist/blocks.js +85 -41
- package/dist/blocks.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +102 -78
- package/dist/index.js.map +1 -1
- package/dist/registry.js +93 -41
- package/dist/registry.js.map +1 -1
- package/dist/renderer.js +102 -78
- package/dist/renderer.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,18 +26,62 @@ function Heading({
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// src/blocks/portableText.tsx
|
|
30
|
+
import React from "react";
|
|
31
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
32
|
+
var MARK_TAGS = {
|
|
33
|
+
strong: "strong",
|
|
34
|
+
em: "em",
|
|
35
|
+
underline: "u",
|
|
36
|
+
"strike-through": "s",
|
|
37
|
+
code: "code"
|
|
38
|
+
};
|
|
39
|
+
var BLOCK_TAGS = {
|
|
40
|
+
normal: "p",
|
|
41
|
+
h1: "h1",
|
|
42
|
+
h2: "h2",
|
|
43
|
+
h3: "h3",
|
|
44
|
+
h4: "h4",
|
|
45
|
+
h5: "h5",
|
|
46
|
+
h6: "h6",
|
|
47
|
+
blockquote: "blockquote"
|
|
48
|
+
};
|
|
49
|
+
function Span({ node, index }) {
|
|
50
|
+
const marks = node.marks ?? [];
|
|
51
|
+
const colour = marks.find((m) => m.startsWith("color:"))?.slice("color:".length);
|
|
52
|
+
let out = node.text ?? "";
|
|
53
|
+
for (const m of marks) {
|
|
54
|
+
const Tag = MARK_TAGS[m];
|
|
55
|
+
if (Tag) out = /* @__PURE__ */ jsx2(Tag, { children: out });
|
|
56
|
+
}
|
|
57
|
+
if (colour && /^[a-z0-9-]+$/i.test(colour)) {
|
|
58
|
+
out = /* @__PURE__ */ jsx2("span", { className: `bcms-color-${colour}`, children: out });
|
|
59
|
+
}
|
|
60
|
+
return /* @__PURE__ */ jsx2(React.Fragment, { children: out }, node._key ?? index);
|
|
61
|
+
}
|
|
62
|
+
function PortableText({ doc }) {
|
|
63
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: doc.content.map((block, i) => {
|
|
64
|
+
const Tag = BLOCK_TAGS[block.style ?? "normal"] ?? "p";
|
|
65
|
+
return /* @__PURE__ */ jsx2(Tag, { children: (block.children ?? []).map((c, j) => /* @__PURE__ */ jsx2(Span, { node: c, index: j }, c._key ?? j)) }, block._key ?? i);
|
|
66
|
+
}) });
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
// src/blocks/Text.tsx
|
|
30
|
-
import { jsx as
|
|
31
|
-
function Text({ html, align = "left", className = "" }) {
|
|
70
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
71
|
+
function Text({ html, content, align = "left", className = "" }) {
|
|
32
72
|
const alignClass = {
|
|
33
73
|
left: "text-left",
|
|
34
74
|
center: "text-center",
|
|
35
75
|
right: "text-right"
|
|
36
76
|
}[align];
|
|
37
|
-
|
|
77
|
+
const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;
|
|
78
|
+
if (content && content.content.length > 0) {
|
|
79
|
+
return /* @__PURE__ */ jsx3("div", { className: cls, children: /* @__PURE__ */ jsx3(PortableText, { doc: content }) });
|
|
80
|
+
}
|
|
81
|
+
return /* @__PURE__ */ jsx3(
|
|
38
82
|
"div",
|
|
39
83
|
{
|
|
40
|
-
className:
|
|
84
|
+
className: cls,
|
|
41
85
|
dangerouslySetInnerHTML: { __html: html }
|
|
42
86
|
}
|
|
43
87
|
);
|
|
@@ -51,7 +95,7 @@ function cn(...inputs) {
|
|
|
51
95
|
}
|
|
52
96
|
|
|
53
97
|
// src/blocks/Image.tsx
|
|
54
|
-
import { jsx as
|
|
98
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
55
99
|
function Image({
|
|
56
100
|
src,
|
|
57
101
|
alt,
|
|
@@ -62,7 +106,7 @@ function Image({
|
|
|
62
106
|
sizes
|
|
63
107
|
}) {
|
|
64
108
|
return /* @__PURE__ */ jsxs("figure", { className: cn("w-full", className), children: [
|
|
65
|
-
/* @__PURE__ */
|
|
109
|
+
/* @__PURE__ */ jsx4(
|
|
66
110
|
"img",
|
|
67
111
|
{
|
|
68
112
|
src,
|
|
@@ -74,12 +118,12 @@ function Image({
|
|
|
74
118
|
loading: "lazy"
|
|
75
119
|
}
|
|
76
120
|
),
|
|
77
|
-
caption && /* @__PURE__ */
|
|
121
|
+
caption && /* @__PURE__ */ jsx4("figcaption", { className: "mt-2 text-sm text-muted-foreground text-center italic", children: caption })
|
|
78
122
|
] });
|
|
79
123
|
}
|
|
80
124
|
|
|
81
125
|
// src/blocks/Button.tsx
|
|
82
|
-
import { jsx as
|
|
126
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
83
127
|
function Button({
|
|
84
128
|
text,
|
|
85
129
|
href,
|
|
@@ -97,7 +141,7 @@ function Button({
|
|
|
97
141
|
center: "justify-center",
|
|
98
142
|
right: "justify-end"
|
|
99
143
|
}[align];
|
|
100
|
-
return /* @__PURE__ */
|
|
144
|
+
return /* @__PURE__ */ jsx5(
|
|
101
145
|
"a",
|
|
102
146
|
{
|
|
103
147
|
href,
|
|
@@ -108,9 +152,9 @@ function Button({
|
|
|
108
152
|
}
|
|
109
153
|
|
|
110
154
|
// src/blocks/Spacer.tsx
|
|
111
|
-
import { jsx as
|
|
155
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
112
156
|
function Spacer({ height = 32, className = "" }) {
|
|
113
|
-
return /* @__PURE__ */
|
|
157
|
+
return /* @__PURE__ */ jsx6(
|
|
114
158
|
"div",
|
|
115
159
|
{
|
|
116
160
|
className: `w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`,
|
|
@@ -126,14 +170,14 @@ function Spacer({ height = 32, className = "" }) {
|
|
|
126
170
|
|
|
127
171
|
// src/blocks/Columns.tsx
|
|
128
172
|
import "react";
|
|
129
|
-
import { jsx as
|
|
173
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
130
174
|
function Columns({
|
|
131
175
|
columns,
|
|
132
176
|
gap = 16,
|
|
133
177
|
className = "",
|
|
134
178
|
renderBlock
|
|
135
179
|
}) {
|
|
136
|
-
return /* @__PURE__ */
|
|
180
|
+
return /* @__PURE__ */ jsx7(
|
|
137
181
|
"div",
|
|
138
182
|
{
|
|
139
183
|
className: cn("grid w-full", className),
|
|
@@ -141,14 +185,14 @@ function Columns({
|
|
|
141
185
|
gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,
|
|
142
186
|
gap: `${gap}px`
|
|
143
187
|
},
|
|
144
|
-
children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */
|
|
188
|
+
children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */ jsx7("div", { className: "flex flex-col gap-4 min-w-0", children: columnBlocks.map((block) => /* @__PURE__ */ jsx7("span", { children: renderBlock ? renderBlock(block) : null }, block.id)) }, colIndex))
|
|
145
189
|
}
|
|
146
190
|
);
|
|
147
191
|
}
|
|
148
192
|
|
|
149
193
|
// src/blocks/Video.tsx
|
|
150
194
|
import { useState } from "react";
|
|
151
|
-
import { jsx as
|
|
195
|
+
import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
152
196
|
function getEmbedUrl(url) {
|
|
153
197
|
const ytMatch = url.match(
|
|
154
198
|
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
|
|
@@ -178,7 +222,7 @@ function Video({
|
|
|
178
222
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
179
223
|
const { embedUrl, provider } = getEmbedUrl(url);
|
|
180
224
|
if (provider === "unknown") {
|
|
181
|
-
return /* @__PURE__ */
|
|
225
|
+
return /* @__PURE__ */ jsx8(
|
|
182
226
|
"video",
|
|
183
227
|
{
|
|
184
228
|
src: url,
|
|
@@ -193,7 +237,7 @@ function Video({
|
|
|
193
237
|
}
|
|
194
238
|
return /* @__PURE__ */ jsxs3("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
|
|
195
239
|
!isLoaded && poster && // eslint-disable-next-line @next/next/no-img-element
|
|
196
|
-
/* @__PURE__ */
|
|
240
|
+
/* @__PURE__ */ jsx8(
|
|
197
241
|
"img",
|
|
198
242
|
{
|
|
199
243
|
src: poster,
|
|
@@ -201,7 +245,7 @@ function Video({
|
|
|
201
245
|
className: "w-full h-full object-cover absolute inset-0"
|
|
202
246
|
}
|
|
203
247
|
),
|
|
204
|
-
/* @__PURE__ */
|
|
248
|
+
/* @__PURE__ */ jsx8(
|
|
205
249
|
"iframe",
|
|
206
250
|
{
|
|
207
251
|
src: `${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`,
|
|
@@ -217,7 +261,7 @@ function Video({
|
|
|
217
261
|
|
|
218
262
|
// src/blocks/Section.tsx
|
|
219
263
|
import "react";
|
|
220
|
-
import { jsx as
|
|
264
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
221
265
|
function Section({
|
|
222
266
|
children,
|
|
223
267
|
background,
|
|
@@ -226,24 +270,24 @@ function Section({
|
|
|
226
270
|
className = "",
|
|
227
271
|
renderBlock
|
|
228
272
|
}) {
|
|
229
|
-
const inner = (children ?? []).map((block) => /* @__PURE__ */
|
|
230
|
-
return /* @__PURE__ */
|
|
273
|
+
const inner = (children ?? []).map((block) => /* @__PURE__ */ jsx9("span", { children: renderBlock ? renderBlock(block) : null }, block.id));
|
|
274
|
+
return /* @__PURE__ */ jsx9(
|
|
231
275
|
"section",
|
|
232
276
|
{
|
|
233
277
|
className: cn("w-full", className),
|
|
234
278
|
style: { background, paddingTop: paddingY, paddingBottom: paddingY },
|
|
235
|
-
children: maxWidth ? /* @__PURE__ */
|
|
279
|
+
children: maxWidth ? /* @__PURE__ */ jsx9("div", { style: { maxWidth, marginInline: "auto" }, children: inner }) : inner
|
|
236
280
|
}
|
|
237
281
|
);
|
|
238
282
|
}
|
|
239
283
|
|
|
240
284
|
// src/blocks/Navbar.tsx
|
|
241
|
-
import { jsx as
|
|
285
|
+
import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
242
286
|
function Navbar({ logo, links, cta, className = "" }) {
|
|
243
287
|
return /* @__PURE__ */ jsxs4("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
|
|
244
|
-
logo?.src ? /* @__PURE__ */
|
|
245
|
-
/* @__PURE__ */
|
|
246
|
-
cta ? /* @__PURE__ */
|
|
288
|
+
logo?.src ? /* @__PURE__ */ jsx10("a", { href: logo.href ?? "/", className: "shrink-0", children: /* @__PURE__ */ jsx10("img", { src: logo.src, alt: logo.alt, className: "h-8 w-auto" }) }) : null,
|
|
289
|
+
/* @__PURE__ */ jsx10("div", { className: "flex flex-1 gap-5", children: (links ?? []).map((l, i) => /* @__PURE__ */ jsx10("a", { href: l.href, className: "text-inherit no-underline", children: l.label }, i)) }),
|
|
290
|
+
cta ? /* @__PURE__ */ jsx10(
|
|
247
291
|
"a",
|
|
248
292
|
{
|
|
249
293
|
href: cta.href,
|
|
@@ -255,40 +299,40 @@ function Navbar({ logo, links, cta, className = "" }) {
|
|
|
255
299
|
}
|
|
256
300
|
|
|
257
301
|
// src/blocks/Footer.tsx
|
|
258
|
-
import { jsx as
|
|
302
|
+
import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
259
303
|
function Footer({ columns, copyright, className = "" }) {
|
|
260
304
|
return /* @__PURE__ */ jsxs5("footer", { className: cn("mt-12 border-t pt-8", className), children: [
|
|
261
|
-
/* @__PURE__ */
|
|
262
|
-
col.heading ? /* @__PURE__ */
|
|
263
|
-
(col.links ?? []).map((l, j) => /* @__PURE__ */
|
|
305
|
+
/* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1.5", children: [
|
|
306
|
+
col.heading ? /* @__PURE__ */ jsx11("h4", { className: "mb-2 font-semibold", children: col.heading }) : null,
|
|
307
|
+
(col.links ?? []).map((l, j) => /* @__PURE__ */ jsx11("a", { href: l.href, className: "text-neutral-500 no-underline", children: l.label }, j))
|
|
264
308
|
] }, i)) }),
|
|
265
|
-
copyright ? /* @__PURE__ */
|
|
309
|
+
copyright ? /* @__PURE__ */ jsx11("p", { className: "mt-6 text-sm text-neutral-500", children: copyright }) : null
|
|
266
310
|
] });
|
|
267
311
|
}
|
|
268
312
|
|
|
269
313
|
// src/blocks/Slider.tsx
|
|
270
314
|
import { useState as useState2 } from "react";
|
|
271
|
-
import { jsx as
|
|
315
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
272
316
|
function Slider({ slides, className = "", renderBlock }) {
|
|
273
317
|
const [i, setI] = useState2(0);
|
|
274
318
|
const list = slides ?? [];
|
|
275
319
|
const go = (n) => setI((n + list.length) % (list.length || 1));
|
|
276
320
|
const btn = "absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer";
|
|
277
321
|
return /* @__PURE__ */ jsxs6("div", { className: cn("relative w-full", className), children: [
|
|
278
|
-
list.map((s, idx) => /* @__PURE__ */
|
|
279
|
-
/* @__PURE__ */
|
|
280
|
-
/* @__PURE__ */
|
|
322
|
+
list.map((s, idx) => /* @__PURE__ */ jsx12("div", { hidden: idx !== i, className: "flex flex-col gap-4", children: (s.children ?? []).map((b) => /* @__PURE__ */ jsx12("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, s.id)),
|
|
323
|
+
/* @__PURE__ */ jsx12("button", { type: "button", "aria-label": "Previous", onClick: () => go(i - 1), className: cn(btn, "left-2"), children: "\u2039" }),
|
|
324
|
+
/* @__PURE__ */ jsx12("button", { type: "button", "aria-label": "Next", onClick: () => go(i + 1), className: cn(btn, "right-2"), children: "\u203A" })
|
|
281
325
|
] });
|
|
282
326
|
}
|
|
283
327
|
|
|
284
328
|
// src/blocks/Tabs.tsx
|
|
285
329
|
import { useState as useState3 } from "react";
|
|
286
|
-
import { jsx as
|
|
330
|
+
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
287
331
|
function Tabs({ tabs, className = "", renderBlock }) {
|
|
288
332
|
const [active, setActive] = useState3(0);
|
|
289
333
|
const list = tabs ?? [];
|
|
290
334
|
return /* @__PURE__ */ jsxs7("div", { className: cn("w-full", className), children: [
|
|
291
|
-
/* @__PURE__ */
|
|
335
|
+
/* @__PURE__ */ jsx13("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx13(
|
|
292
336
|
"button",
|
|
293
337
|
{
|
|
294
338
|
role: "tab",
|
|
@@ -303,12 +347,12 @@ function Tabs({ tabs, className = "", renderBlock }) {
|
|
|
303
347
|
},
|
|
304
348
|
t.id
|
|
305
349
|
)) }),
|
|
306
|
-
list.map((t, idx) => /* @__PURE__ */
|
|
350
|
+
list.map((t, idx) => /* @__PURE__ */ jsx13("div", { role: "tabpanel", hidden: idx !== active, className: "flex flex-col gap-4", children: (t.children ?? []).map((b) => /* @__PURE__ */ jsx13("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, t.id))
|
|
307
351
|
] });
|
|
308
352
|
}
|
|
309
353
|
|
|
310
354
|
// src/blocks/Placeholders.tsx
|
|
311
|
-
import { jsx as
|
|
355
|
+
import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
312
356
|
var box = "rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500";
|
|
313
357
|
function ComponentInstance({
|
|
314
358
|
componentId,
|
|
@@ -316,13 +360,13 @@ function ComponentInstance({
|
|
|
316
360
|
}) {
|
|
317
361
|
return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
|
|
318
362
|
"Component instance: ",
|
|
319
|
-
/* @__PURE__ */
|
|
363
|
+
/* @__PURE__ */ jsx14("code", { children: componentId })
|
|
320
364
|
] });
|
|
321
365
|
}
|
|
322
366
|
function FormPlaceholder({ formId, className = "" }) {
|
|
323
367
|
return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
|
|
324
368
|
"Form: ",
|
|
325
|
-
/* @__PURE__ */
|
|
369
|
+
/* @__PURE__ */ jsx14("code", { children: formId })
|
|
326
370
|
] });
|
|
327
371
|
}
|
|
328
372
|
|
|
@@ -338,6 +382,14 @@ var BLOCK_REGISTRY = {
|
|
|
338
382
|
label: "Text",
|
|
339
383
|
icon: "align-left"
|
|
340
384
|
},
|
|
385
|
+
// Same payload, same renderer — deliberately. `richtext` differs from `text` only in how
|
|
386
|
+
// an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML
|
|
387
|
+
// string, so duplicating the component would be two things to keep in sync for no gain.
|
|
388
|
+
richtext: {
|
|
389
|
+
component: Text,
|
|
390
|
+
label: "Rich text",
|
|
391
|
+
icon: "pilcrow"
|
|
392
|
+
},
|
|
341
393
|
image: {
|
|
342
394
|
component: Image,
|
|
343
395
|
label: "Image",
|
|
@@ -401,43 +453,15 @@ var BLOCK_REGISTRY = {
|
|
|
401
453
|
};
|
|
402
454
|
var BLOCK_TYPES = Object.keys(BLOCK_REGISTRY);
|
|
403
455
|
|
|
404
|
-
// ../types/dist/index.js
|
|
405
|
-
var SPACE_PX = { none: "0", xs: "4px", sm: "8px", md: "16px", lg: "32px", xl: "64px" };
|
|
406
|
-
var FONT_SIZE_REM = { sm: "0.875rem", base: "1rem", lg: "1.25rem", xl: "1.5rem", "2xl": "2rem" };
|
|
407
|
-
var WEIGHT = { normal: 400, medium: 500, semibold: 600, bold: 700 };
|
|
408
|
-
var RADIUS_PX = { none: "0", sm: "4px", md: "8px", lg: "16px", full: "9999px" };
|
|
409
|
-
var COLOR_HEX = { white: "#ffffff", black: "#111111", muted: "#6b7280", mutedBg: "#f3f4f6", primary: "#111111", accent: "#4f46e5" };
|
|
410
|
-
function blockStyleToCss(style) {
|
|
411
|
-
if (!style || typeof style !== "object") return void 0;
|
|
412
|
-
const s = style;
|
|
413
|
-
const css = {};
|
|
414
|
-
if (s.paddingY && SPACE_PX[s.paddingY] != null) {
|
|
415
|
-
css.paddingTop = SPACE_PX[s.paddingY];
|
|
416
|
-
css.paddingBottom = SPACE_PX[s.paddingY];
|
|
417
|
-
}
|
|
418
|
-
if (s.paddingX && SPACE_PX[s.paddingX] != null) {
|
|
419
|
-
css.paddingLeft = SPACE_PX[s.paddingX];
|
|
420
|
-
css.paddingRight = SPACE_PX[s.paddingX];
|
|
421
|
-
}
|
|
422
|
-
if (s.marginTop && SPACE_PX[s.marginTop] != null) css.marginTop = SPACE_PX[s.marginTop];
|
|
423
|
-
if (s.marginBottom && SPACE_PX[s.marginBottom] != null) css.marginBottom = SPACE_PX[s.marginBottom];
|
|
424
|
-
if (s.background && COLOR_HEX[s.background]) css.background = COLOR_HEX[s.background];
|
|
425
|
-
if (s.textColor && COLOR_HEX[s.textColor]) css.color = COLOR_HEX[s.textColor];
|
|
426
|
-
if (s.align === "left" || s.align === "center" || s.align === "right") css.textAlign = s.align;
|
|
427
|
-
if (s.fontSize && FONT_SIZE_REM[s.fontSize]) css.fontSize = FONT_SIZE_REM[s.fontSize];
|
|
428
|
-
if (s.weight && WEIGHT[s.weight] != null) css.fontWeight = WEIGHT[s.weight];
|
|
429
|
-
if (s.radius && RADIUS_PX[s.radius] != null) css.borderRadius = RADIUS_PX[s.radius];
|
|
430
|
-
return Object.keys(css).length ? css : void 0;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
456
|
// src/blockStyle.ts
|
|
457
|
+
import { blockStyleToCss } from "@bettercms-ai/types";
|
|
434
458
|
function blockStyleToReactStyle(style) {
|
|
435
459
|
return blockStyleToCss(style);
|
|
436
460
|
}
|
|
437
461
|
|
|
438
462
|
// src/renderer.tsx
|
|
439
463
|
import "react";
|
|
440
|
-
import { jsx as
|
|
464
|
+
import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
441
465
|
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["columns", "section", "slider", "tabs"]);
|
|
442
466
|
function resolveBlock(block, className) {
|
|
443
467
|
const entry = BLOCK_REGISTRY[block.type];
|
|
@@ -448,15 +472,15 @@ function resolveBlock(block, className) {
|
|
|
448
472
|
className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? ""}`,
|
|
449
473
|
children: [
|
|
450
474
|
"Unknown block type: ",
|
|
451
|
-
/* @__PURE__ */
|
|
475
|
+
/* @__PURE__ */ jsx15("code", { children: block.type })
|
|
452
476
|
]
|
|
453
477
|
}
|
|
454
478
|
);
|
|
455
479
|
}
|
|
456
480
|
const Component = entry.component;
|
|
457
481
|
const blockStyle = blockStyleToReactStyle(block.style);
|
|
458
|
-
const el = /* @__PURE__ */
|
|
459
|
-
return blockStyle ? /* @__PURE__ */
|
|
482
|
+
const el = /* @__PURE__ */ jsx15(Component, { ...block.props, className });
|
|
483
|
+
return blockStyle ? /* @__PURE__ */ jsx15("div", { style: blockStyle, children: el }) : el;
|
|
460
484
|
}
|
|
461
485
|
function BlockRenderer({
|
|
462
486
|
block,
|
|
@@ -471,7 +495,7 @@ function BlockRenderer({
|
|
|
471
495
|
className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`,
|
|
472
496
|
children: [
|
|
473
497
|
"Unknown block type: ",
|
|
474
|
-
/* @__PURE__ */
|
|
498
|
+
/* @__PURE__ */ jsx15("code", { children: block.type })
|
|
475
499
|
]
|
|
476
500
|
}
|
|
477
501
|
);
|
|
@@ -479,8 +503,8 @@ function BlockRenderer({
|
|
|
479
503
|
const Component = entry.component;
|
|
480
504
|
const containerProps = CONTAINER_TYPES.has(block.type) ? { renderBlock: renderBlock ?? resolveBlock } : {};
|
|
481
505
|
const blockStyle = blockStyleToReactStyle(block.style);
|
|
482
|
-
const el = /* @__PURE__ */
|
|
483
|
-
return blockStyle ? /* @__PURE__ */
|
|
506
|
+
const el = /* @__PURE__ */ jsx15(Component, { ...block.props, className, ...containerProps });
|
|
507
|
+
return blockStyle ? /* @__PURE__ */ jsx15("div", { style: blockStyle, children: el }) : el;
|
|
484
508
|
}
|
|
485
509
|
function BlocksRenderer({
|
|
486
510
|
blocks,
|
|
@@ -488,7 +512,7 @@ function BlocksRenderer({
|
|
|
488
512
|
renderBlock
|
|
489
513
|
}) {
|
|
490
514
|
if (blocks.length === 0) return null;
|
|
491
|
-
return /* @__PURE__ */
|
|
515
|
+
return /* @__PURE__ */ jsx15("div", { className, children: blocks.map((block) => /* @__PURE__ */ jsx15(
|
|
492
516
|
BlockRenderer,
|
|
493
517
|
{
|
|
494
518
|
block,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/Text.tsx","../src/lib/utils.ts","../src/blocks/Image.tsx","../src/blocks/Button.tsx","../src/blocks/Spacer.tsx","../src/blocks/Columns.tsx","../src/blocks/Video.tsx","../src/blocks/Section.tsx","../src/blocks/Navbar.tsx","../src/blocks/Footer.tsx","../src/blocks/Slider.tsx","../src/blocks/Tabs.tsx","../src/blocks/Placeholders.tsx","../src/registry.ts","../../types/src/block.ts","../../types/src/blockStyle.ts","../src/blockStyle.ts","../src/renderer.tsx"],"sourcesContent":["import type { HeadingProps } from \"@betttercms/types\";\n\ninterface HeadingBlockProps extends HeadingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Heading({\n text,\n level = 2,\n align = \"left\",\n className = \"\",\n}: HeadingBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;\n\n switch (level) {\n case 1:\n return <h1 className={classNames}>{text}</h1>;\n case 2:\n return <h2 className={classNames}>{text}</h2>;\n case 3:\n return <h3 className={classNames}>{text}</h3>;\n default:\n return <h2 className={classNames}>{text}</h2>;\n }\n}\n","import type { TextProps } from \"@betttercms/types\";\n\ninterface TextBlockProps extends TextProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Text({ html, align = \"left\", className = \"\" }: TextBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n return (\n <div\n className={`text-muted-foreground leading-relaxed ${alignClass} ${className}`}\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { ImageProps } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface ImageBlockProps extends ImageProps {\n className?: string;\n sizes?: string;\n}\n\nexport function Image({\n src,\n alt,\n width,\n height,\n caption,\n className = \"\",\n sizes,\n}: ImageBlockProps) {\n return (\n <figure className={cn(\"w-full\", className)}>\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n className=\"w-full h-auto rounded-md object-cover\"\n loading=\"lazy\"\n />\n {caption && (\n <figcaption className=\"mt-2 text-sm text-muted-foreground text-center italic\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n","import type { ButtonProps } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface ButtonBlockProps extends ButtonProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Button({\n text,\n href,\n variant = \"primary\",\n align = \"left\",\n className = \"\",\n}: ButtonBlockProps) {\n const baseStyles =\n \"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\";\n\n const variantStyles = {\n primary:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n };\n\n const alignClass = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n }[align];\n\n return (\n <a\n href={href}\n className={cn(baseStyles, variantStyles[variant], alignClass, \"px-4 py-2\", className)}\n >\n {text}\n </a>\n );\n}\n","import type { SpacerProps } from \"@betttercms/types\";\n\ninterface SpacerBlockProps extends SpacerProps {\n className?: string;\n}\n\nexport function Spacer({ height = 32, className = \"\" }: SpacerBlockProps) {\n return (\n <div\n className={`w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`}\n style={{ height: `${height}px` }}\n aria-hidden=\"true\"\n >\n <span className=\"text-xs text-muted-foreground/40 select-none\">\n {height}px\n </span>\n </div>\n );\n}\n","import type { ColumnsProps, ContentBlock } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface ColumnsBlockProps extends ColumnsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Columns({\n columns,\n gap = 16,\n className = \"\",\n renderBlock,\n}: ColumnsBlockProps) {\n return (\n <div\n className={cn(\"grid w-full\", className)}\n style={{\n gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,\n gap: `${gap}px`,\n }}\n >\n {columns.map((columnBlocks, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-4 min-w-0\">\n {columnBlocks.map((block) => (\n <span key={block.id}>\n {renderBlock ? renderBlock(block) : null}\n </span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { VideoProps } from \"@betttercms/types\";\nimport { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ninterface VideoBlockProps extends VideoProps {\n className?: string;\n}\n\nfunction getEmbedUrl(url: string): { embedUrl: string; provider: \"youtube\" | \"vimeo\" | \"unknown\" } {\n // YouTube\n const ytMatch = url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/\n );\n if (ytMatch) {\n return {\n embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,\n provider: \"youtube\",\n };\n }\n\n // Vimeo\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) {\n return {\n embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,\n provider: \"vimeo\",\n };\n }\n\n return { embedUrl: url, provider: \"unknown\" };\n}\n\nexport function Video({\n url,\n poster,\n autoplay = false,\n loop = false,\n className = \"\",\n}: VideoBlockProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const { embedUrl, provider } = getEmbedUrl(url);\n\n // If not a recognized embed, render a native video tag\n if (provider === \"unknown\") {\n return (\n <video\n src={url}\n poster={poster}\n autoPlay={autoplay}\n loop={loop}\n controls\n className={className}\n playsInline\n />\n );\n }\n\n return (\n <div className={cn(\"w-full overflow-hidden rounded-md bg-black aspect-video\", className)}>\n {!isLoaded && poster && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={poster}\n alt=\"Video thumbnail\"\n className=\"w-full h-full object-cover absolute inset-0\"\n />\n )}\n <iframe\n src={`${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n loading=\"lazy\"\n onLoad={() => setIsLoaded(true)}\n className=\"w-full h-full\"\n />\n </div>\n );\n}\n","import type { SectionProps, ContentBlock } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface SectionBlockProps extends SectionProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Section({\n children,\n background,\n paddingY,\n maxWidth,\n className = \"\",\n renderBlock,\n}: SectionBlockProps) {\n const inner = (children ?? []).map((block) => (\n <span key={block.id}>{renderBlock ? renderBlock(block) : null}</span>\n ));\n return (\n <section\n className={cn(\"w-full\", className)}\n style={{ background, paddingTop: paddingY, paddingBottom: paddingY }}\n >\n {maxWidth ? <div style={{ maxWidth, marginInline: \"auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n","import type { NavbarProps } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface NavbarBlockProps extends NavbarProps {\n className?: string;\n}\n\nexport function Navbar({ logo, links, cta, className = \"\" }: NavbarBlockProps) {\n return (\n <nav className={cn(\"flex items-center gap-6 py-4\", className)}>\n {logo?.src ? (\n <a href={logo.href ?? \"/\"} className=\"shrink-0\">\n <img src={logo.src} alt={logo.alt} className=\"h-8 w-auto\" />\n </a>\n ) : null}\n <div className=\"flex flex-1 gap-5\">\n {(links ?? []).map((l, i) => (\n <a key={i} href={l.href} className=\"text-inherit no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n {cta ? (\n <a\n href={cta.href}\n className=\"rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline\"\n >\n {cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n","import type { FooterProps } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface FooterBlockProps extends FooterProps {\n className?: string;\n}\n\nexport function Footer({ columns, copyright, className = \"\" }: FooterBlockProps) {\n return (\n <footer className={cn(\"mt-12 border-t pt-8\", className)}>\n <div className=\"flex flex-wrap gap-12\">\n {(columns ?? []).map((col, i) => (\n <div key={i} className=\"flex flex-col gap-1.5\">\n {col.heading ? <h4 className=\"mb-2 font-semibold\">{col.heading}</h4> : null}\n {(col.links ?? []).map((l, j) => (\n <a key={j} href={l.href} className=\"text-neutral-500 no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n ))}\n </div>\n {copyright ? <p className=\"mt-6 text-sm text-neutral-500\">{copyright}</p> : null}\n </footer>\n );\n}\n","\"use client\";\n\nimport type { SliderProps, ContentBlock } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface SliderBlockProps extends SliderProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Slider({ slides, className = \"\", renderBlock }: SliderBlockProps) {\n const [i, setI] = useState(0);\n const list = slides ?? [];\n const go = (n: number) => setI((n + list.length) % (list.length || 1));\n const btn =\n \"absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer\";\n return (\n <div className={cn(\"relative w-full\", className)}>\n {list.map((s, idx) => (\n <div key={s.id} hidden={idx !== i} className=\"flex flex-col gap-4\">\n {(s.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n <button type=\"button\" aria-label=\"Previous\" onClick={() => go(i - 1)} className={cn(btn, \"left-2\")}>\n ‹\n </button>\n <button type=\"button\" aria-label=\"Next\" onClick={() => go(i + 1)} className={cn(btn, \"right-2\")}>\n ›\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport type { TabsProps, ContentBlock } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface TabsBlockProps extends TabsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Tabs({ tabs, className = \"\", renderBlock }: TabsBlockProps) {\n const [active, setActive] = useState(0);\n const list = tabs ?? [];\n return (\n <div className={cn(\"w-full\", className)}>\n <div role=\"tablist\" className=\"mb-4 flex gap-1 border-b\">\n {list.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n className={cn(\n \"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2\",\n idx === active && \"border-black font-semibold\",\n )}\n >\n {t.label}\n </button>\n ))}\n </div>\n {list.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" hidden={idx !== active} className=\"flex flex-col gap-4\">\n {(t.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { ComponentProps as BcmsComponentProps, FormProps } from \"@betttercms/types\";\nimport { cn } from \"../lib/utils\";\n\nconst box =\n \"rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500\";\n\n/**\n * Builder-preview placeholder for a `component` instance. Live instance resolution\n * (definition lookup + override merge) happens server-side in the delivery renderer\n * and in the page-builder, which pass a resolver — the standalone library preview\n * just shows what's referenced.\n */\nexport function ComponentInstance({\n componentId,\n className = \"\",\n}: BcmsComponentProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Component instance: <code>{componentId}</code>\n </div>\n );\n}\n\n/** Builder-preview placeholder for a `form` block (rendered live by @betttercms/next). */\nexport function FormPlaceholder({ formId, className = \"\" }: FormProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Form: <code>{formId}</code>\n </div>\n );\n}\n","import type { ContentBlock, BlockType } from \"@bettercms-ai/types\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Text } from \"./blocks/Text\";\nimport { Image } from \"./blocks/Image\";\nimport { Button } from \"./blocks/Button\";\nimport { Spacer } from \"./blocks/Spacer\";\nimport { Columns } from \"./blocks/Columns\";\nimport { Video } from \"./blocks/Video\";\nimport { Section } from \"./blocks/Section\";\nimport { Navbar } from \"./blocks/Navbar\";\nimport { Footer } from \"./blocks/Footer\";\nimport { Slider } from \"./blocks/Slider\";\nimport { Tabs } from \"./blocks/Tabs\";\nimport { ComponentInstance, FormPlaceholder } from \"./blocks/Placeholders\";\n\n/**\n * Block registry entry — maps a block type to its React component\n * and metadata for the block library UI.\n */\nexport interface BlockRegistryEntry {\n component: React.ComponentType<Record<string, unknown>>;\n label: string;\n icon: string;\n}\n\nexport const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry> = {\n heading: {\n component: Heading as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Heading\",\n icon: \"type\",\n },\n text: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Text\",\n icon: \"align-left\",\n },\n image: {\n component: Image as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Image\",\n icon: \"image\",\n },\n button: {\n component: Button as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Button\",\n icon: \"square\",\n },\n spacer: {\n component: Spacer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Spacer\",\n icon: \"minus\",\n },\n columns: {\n component: Columns as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Columns\",\n icon: \"columns\",\n },\n video: {\n component: Video as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Video\",\n icon: \"play\",\n },\n section: {\n component: Section as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Section\",\n icon: \"layout\",\n },\n navbar: {\n component: Navbar as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Navbar\",\n icon: \"menu\",\n },\n footer: {\n component: Footer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Footer\",\n icon: \"panel-bottom\",\n },\n slider: {\n component: Slider as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Slider\",\n icon: \"gallery-horizontal\",\n },\n tabs: {\n component: Tabs as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Tabs\",\n icon: \"folder\",\n },\n component: {\n component: ComponentInstance as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Component\",\n icon: \"component\",\n },\n form: {\n component: FormPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Form\",\n icon: \"clipboard-list\",\n },\n} as const;\n\nexport const BLOCK_TYPES = Object.keys(BLOCK_REGISTRY) as BlockType[];\n\nexport type { ContentBlock, BlockType };\n","/**\n * ContentBlock — a discriminated union of all CMS block types.\n * Each block has a `type` discriminator and a `props` object\n * whose shape matches the block type.\n */\n\n// ── Block style (FLO-264) ──────────────────────────────────────────────────────\n// A uniform, BOUNDED set of design tokens any block may carry (sibling to `props`).\n// Tokens map to fixed CSS values in the renderer (`styleTokensToCss`) so styling\n// reaches the live site WITHOUT arbitrary CSS. Additive + fully optional.\n\nexport type SpaceToken = \"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type ColorToken = \"none\" | \"white\" | \"black\" | \"muted\" | \"mutedBg\" | \"primary\" | \"accent\";\nexport type AlignToken = \"left\" | \"center\" | \"right\";\nexport type FontSizeToken = \"sm\" | \"base\" | \"lg\" | \"xl\" | \"2xl\";\nexport type WeightToken = \"normal\" | \"medium\" | \"semibold\" | \"bold\";\nexport type RadiusToken = \"none\" | \"sm\" | \"md\" | \"lg\" | \"full\";\n\nexport interface BlockStyle {\n paddingY?: SpaceToken;\n paddingX?: SpaceToken;\n marginTop?: SpaceToken;\n marginBottom?: SpaceToken;\n background?: ColorToken;\n textColor?: ColorToken;\n align?: AlignToken;\n fontSize?: FontSizeToken;\n weight?: WeightToken;\n radius?: RadiusToken;\n}\n\n// ── Heading ──────────────────────────────────────────────────────────────────\n\nexport interface HeadingProps {\n text: string;\n level?: 1 | 2 | 3;\n}\n\nexport interface HeadingBlock {\n type: \"heading\";\n id: string;\n style?: BlockStyle;\n props: HeadingProps;\n}\n\n// ── Text ─────────────────────────────────────────────────────────────────────\n\nexport interface TextProps {\n html: string;\n}\n\nexport interface TextBlock {\n type: \"text\";\n id: string;\n style?: BlockStyle;\n props: TextProps;\n}\n\n// ── Image ────────────────────────────────────────────────────────────────────\n\nexport interface ImageProps {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n caption?: string;\n}\n\nexport interface ImageBlock {\n type: \"image\";\n id: string;\n style?: BlockStyle;\n props: ImageProps;\n}\n\n// ── Button ───────────────────────────────────────────────────────────────────\n\nexport interface ButtonProps {\n text: string;\n href: string;\n variant?: \"primary\" | \"secondary\";\n}\n\nexport interface ButtonBlock {\n type: \"button\";\n id: string;\n style?: BlockStyle;\n props: ButtonProps;\n}\n\n// ── Spacer ───────────────────────────────────────────────────────────────────\n\nexport interface SpacerProps {\n height: number; // in px\n}\n\nexport interface SpacerBlock {\n type: \"spacer\";\n id: string;\n style?: BlockStyle;\n props: SpacerProps;\n}\n\n// ── Columns ───────────────────────────────────────────────────────────────────\n\nexport interface ColumnsProps {\n columns: ContentBlock[][]; // array of column slots, each slot is an array of blocks\n gap?: number; // in px\n}\n\nexport interface ColumnsBlock {\n type: \"columns\";\n id: string;\n style?: BlockStyle;\n props: ColumnsProps;\n}\n\n// ── Video ────────────────────────────────────────────────────────────────────\n\nexport interface VideoProps {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n}\n\nexport interface VideoBlock {\n type: \"video\";\n id: string;\n style?: BlockStyle;\n props: VideoProps;\n}\n\n// ── Form ─────────────────────────────────────────────────────────────────────\n\nexport interface FormProps {\n formId: string; // references forms.id (a nanoid). Resolved against delivered forms at render time.\n}\n\nexport interface FormBlock {\n type: \"form\";\n id: string;\n style?: BlockStyle;\n props: FormProps;\n}\n\n// ── Section (recursive container) ──────────────────────────────────────────────\n\nexport interface SectionProps {\n children: ContentBlock[];\n background?: string;\n paddingY?: number; // in px\n maxWidth?: number; // in px\n}\n\nexport interface SectionBlock {\n type: \"section\";\n id: string;\n style?: BlockStyle;\n props: SectionProps;\n}\n\n// ── Navbar ─────────────────────────────────────────────────────────────────────\n\nexport interface NavLink {\n label: string;\n href: string;\n}\n\nexport interface NavbarProps {\n logo?: { src: string; alt: string; href?: string };\n links: NavLink[];\n cta?: { text: string; href: string };\n}\n\nexport interface NavbarBlock {\n type: \"navbar\";\n id: string;\n style?: BlockStyle;\n props: NavbarProps;\n}\n\n// ── Footer ─────────────────────────────────────────────────────────────────────\n\nexport interface FooterColumn {\n heading?: string;\n links: NavLink[];\n}\n\nexport interface FooterProps {\n columns: FooterColumn[];\n copyright?: string;\n}\n\nexport interface FooterBlock {\n type: \"footer\";\n id: string;\n style?: BlockStyle;\n props: FooterProps;\n}\n\n// ── Slider / Carousel (recursive container, client-hydrated) ────────────────────\n\nexport interface SlideItem {\n id: string;\n children: ContentBlock[];\n}\n\nexport interface SliderProps {\n slides: SlideItem[];\n autoplay?: boolean;\n interval?: number; // in ms\n loop?: boolean;\n}\n\nexport interface SliderBlock {\n type: \"slider\";\n id: string;\n style?: BlockStyle;\n props: SliderProps;\n}\n\n// ── Tabs (recursive container, client-hydrated) ─────────────────────────────────\n\nexport interface TabItem {\n id: string;\n label: string;\n children: ContentBlock[];\n}\n\nexport interface TabsProps {\n tabs: TabItem[];\n}\n\nexport interface TabsBlock {\n type: \"tabs\";\n id: string;\n style?: BlockStyle;\n props: TabsProps;\n}\n\n// ── Component instance (Webflow-style reusable symbol) ──────────────────────────\n// References a reusable component by id. `overrides` maps a component prop key\n// (declared in the component's `props[]` allowlist) to a value, applied to the\n// component's block tree at render time. Resolved against delivered components.\n\nexport interface ComponentProps {\n componentId: string;\n overrides?: Record<string, unknown>;\n}\n\nexport interface ComponentBlock {\n type: \"component\";\n id: string;\n style?: BlockStyle;\n props: ComponentProps;\n}\n\n// ── Union ─────────────────────────────────────────────────────────────────────\n\nexport type ContentBlock =\n | HeadingBlock\n | TextBlock\n | ImageBlock\n | ButtonBlock\n | SpacerBlock\n | ColumnsBlock\n | VideoBlock\n | FormBlock\n | SectionBlock\n | NavbarBlock\n | FooterBlock\n | SliderBlock\n | TabsBlock\n | ComponentBlock;\n\n/** Literal union of all block type discriminators. */\nexport type BlockType = ContentBlock[\"type\"];\n\n/**\n * Type guard — narrows an unknown block to `ContentBlock`.\n *\n * @example\n * const block = maybeBlock as unknown;\n * if (isBlock(block)) {\n * // block is ContentBlock here\n * }\n */\nexport function isBlock(value: unknown): value is ContentBlock {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n typeof (value as Record<string, unknown>)[\"type\"] === \"string\"\n );\n}\n\n/**\n * Extract the `type` discriminator from a block, or `null` if not a block.\n *\n * @example\n * const block = { type: \"heading\", id: \"1\", props: { text: \"Hi\" } };\n * getBlockType(block); // \"heading\"\n * getBlockType({ not: \"a block\" }); // null\n */\nexport function getBlockType(value: unknown): BlockType | null {\n if (isBlock(value)) return value.type;\n return null;\n}\n","/**\n * FLO-264 — bounded block-style tokens → CSS. Framework-agnostic (returns a plain\n * declaration object, no React types) so every renderer shares ONE mapping: the\n * headless @betttercms/next adapter, @betttercms/ui, and (string form) the hosted\n * render-page.ts. Unknown tokens are dropped — closed set, no arbitrary CSS.\n */\nimport type { BlockStyle } from \"./block.js\";\n\nconst SPACE_PX: Record<string, string> = { none: \"0\", xs: \"4px\", sm: \"8px\", md: \"16px\", lg: \"32px\", xl: \"64px\" };\nconst FONT_SIZE_REM: Record<string, string> = { sm: \"0.875rem\", base: \"1rem\", lg: \"1.25rem\", xl: \"1.5rem\", \"2xl\": \"2rem\" };\nconst WEIGHT: Record<string, number> = { normal: 400, medium: 500, semibold: 600, bold: 700 };\nconst RADIUS_PX: Record<string, string> = { none: \"0\", sm: \"4px\", md: \"8px\", lg: \"16px\", full: \"9999px\" };\nconst COLOR_HEX: Record<string, string> = { white: \"#ffffff\", black: \"#111111\", muted: \"#6b7280\", mutedBg: \"#f3f4f6\", primary: \"#111111\", accent: \"#4f46e5\" };\n\n/** A CSS declaration object (key → value) usable as a React `style` prop, or undefined when empty. */\nexport function blockStyleToCss(style: BlockStyle | undefined): Record<string, string | number> | undefined {\n if (!style || typeof style !== \"object\") return undefined;\n const s = style as Record<string, string>;\n const css: Record<string, string | number> = {};\n if (s.paddingY && SPACE_PX[s.paddingY] != null) { css.paddingTop = SPACE_PX[s.paddingY]!; css.paddingBottom = SPACE_PX[s.paddingY]!; }\n if (s.paddingX && SPACE_PX[s.paddingX] != null) { css.paddingLeft = SPACE_PX[s.paddingX]!; css.paddingRight = SPACE_PX[s.paddingX]!; }\n if (s.marginTop && SPACE_PX[s.marginTop] != null) css.marginTop = SPACE_PX[s.marginTop]!;\n if (s.marginBottom && SPACE_PX[s.marginBottom] != null) css.marginBottom = SPACE_PX[s.marginBottom]!;\n if (s.background && COLOR_HEX[s.background]) css.background = COLOR_HEX[s.background]!;\n if (s.textColor && COLOR_HEX[s.textColor]) css.color = COLOR_HEX[s.textColor]!;\n if (s.align === \"left\" || s.align === \"center\" || s.align === \"right\") css.textAlign = s.align;\n if (s.fontSize && FONT_SIZE_REM[s.fontSize]) css.fontSize = FONT_SIZE_REM[s.fontSize]!;\n if (s.weight && WEIGHT[s.weight] != null) css.fontWeight = WEIGHT[s.weight]!;\n if (s.radius && RADIUS_PX[s.radius] != null) css.borderRadius = RADIUS_PX[s.radius]!;\n return Object.keys(css).length ? css : undefined;\n}\n","import type { CSSProperties } from \"react\";\nimport { blockStyleToCss, type BlockStyle } from \"@betttercms/types\";\n\n/**\n * FLO-264 — React-typed wrapper over the shared, framework-agnostic\n * `blockStyleToCss` in @betttercms/types. One mapping, three consumers (this UI\n * renderer, the @betttercms/next adapter, and — in string form — the hosted\n * render-page.ts).\n */\nexport function blockStyleToReactStyle(style: BlockStyle | undefined): CSSProperties | undefined {\n return blockStyleToCss(style) as CSSProperties | undefined;\n}\n","import type { ContentBlock } from \"@bettercms-ai/types\";\nimport { BLOCK_REGISTRY, type BlockRegistryEntry } from \"./registry\";\nimport { blockStyleToReactStyle } from \"./blockStyle\";\nimport React from \"react\";\n\n// Container blocks hold nested blocks and need the `renderBlock` callback threaded\n// so their children render recursively.\nconst CONTAINER_TYPES = new Set([\"columns\", \"section\", \"slider\", \"tabs\"]);\n\ninterface BlockRendererProps {\n block: ContentBlock;\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nfunction resolveBlock(block: ContentBlock, className?: string): React.ReactNode {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? \"\"}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n const Component = entry.component;\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = <Component {...block.props} className={className} />;\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\nexport function BlockRenderer({\n block,\n className = \"\",\n renderBlock,\n}: BlockRendererProps) {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n // Container blocks (columns/section/slider/tabs) hold nested blocks, so thread the\n // renderBlock callback. Standalone usage (no BlocksRenderer above) falls back to\n // resolveBlock so one level of nesting still renders.\n const Component = entry.component;\n const containerProps = CONTAINER_TYPES.has(block.type)\n ? { renderBlock: renderBlock ?? resolveBlock }\n : {};\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = <Component {...block.props} className={className} {...containerProps} />;\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\ninterface BlocksRendererProps {\n blocks: ContentBlock[];\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function BlocksRenderer({\n blocks,\n className = \"\",\n renderBlock,\n}: BlocksRendererProps) {\n if (blocks.length === 0) return null;\n\n return (\n <div className={className}>\n {blocks.map((block) => (\n <BlockRenderer\n key={block.id}\n block={block}\n renderBlock={renderBlock}\n />\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsx","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/portableText.tsx","../src/blocks/Text.tsx","../src/lib/utils.ts","../src/blocks/Image.tsx","../src/blocks/Button.tsx","../src/blocks/Spacer.tsx","../src/blocks/Columns.tsx","../src/blocks/Video.tsx","../src/blocks/Section.tsx","../src/blocks/Navbar.tsx","../src/blocks/Footer.tsx","../src/blocks/Slider.tsx","../src/blocks/Tabs.tsx","../src/blocks/Placeholders.tsx","../src/registry.ts","../src/blockStyle.ts","../src/renderer.tsx"],"sourcesContent":["import type { HeadingProps } from \"@bettercms-ai/types\";\n\ninterface HeadingBlockProps extends HeadingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Heading({\n text,\n level = 2,\n align = \"left\",\n className = \"\",\n}: HeadingBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;\n\n switch (level) {\n case 1:\n return <h1 className={classNames}>{text}</h1>;\n case 2:\n return <h2 className={classNames}>{text}</h2>;\n case 3:\n return <h3 className={classNames}>{text}</h3>;\n default:\n return <h2 className={classNames}>{text}</h2>;\n }\n}\n","import React from \"react\";\nimport type { PortableTextDocument, PortableTextNode } from \"@bettercms-ai/types\";\n\n/**\n * Portable Text → React.\n *\n * React rather than an HTML string, deliberately: React escapes text children by default, so\n * author data cannot become markup here at all. The `dangerouslySetInnerHTML` path that the\n * `html` cache still uses has to be trusted; this one does not.\n *\n * Unknown marks and unknown styles are DROPPED, never passed through. A colour mark carries a\n * token NAME (`color:brand-accent`) which becomes a class the consuming site styles — so it is\n * theme-aware and survives a rebrand, which a baked hex does not.\n */\n\nconst MARK_TAGS: Record<string, keyof React.JSX.IntrinsicElements> = {\n strong: \"strong\",\n em: \"em\",\n underline: \"u\",\n \"strike-through\": \"s\",\n code: \"code\",\n};\n\nconst BLOCK_TAGS: Record<string, keyof React.JSX.IntrinsicElements> = {\n normal: \"p\",\n h1: \"h1\",\n h2: \"h2\",\n h3: \"h3\",\n h4: \"h4\",\n h5: \"h5\",\n h6: \"h6\",\n blockquote: \"blockquote\",\n};\n\nfunction Span({ node, index }: { node: PortableTextNode; index: number }) {\n const marks = node.marks ?? [];\n const colour = marks.find((m) => m.startsWith(\"color:\"))?.slice(\"color:\".length);\n\n // Text is a React child, so it is escaped by construction.\n let out: React.ReactNode = node.text ?? \"\";\n for (const m of marks) {\n const Tag = MARK_TAGS[m];\n if (Tag) out = <Tag>{out}</Tag>;\n }\n if (colour && /^[a-z0-9-]+$/i.test(colour)) {\n out = <span className={`bcms-color-${colour}`}>{out}</span>;\n }\n return <React.Fragment key={node._key ?? index}>{out}</React.Fragment>;\n}\n\nexport function PortableText({ doc }: { doc: PortableTextDocument }) {\n return (\n <>\n {doc.content.map((block, i) => {\n const Tag = BLOCK_TAGS[block.style ?? \"normal\"] ?? \"p\";\n return (\n <Tag key={block._key ?? i}>\n {(block.children ?? []).map((c, j) => (\n <Span key={c._key ?? j} node={c} index={j} />\n ))}\n </Tag>\n );\n })}\n </>\n );\n}\n","import type { PortableTextDocument, TextProps } from \"@bettercms-ai/types\";\nimport { PortableText } from \"./portableText\";\n\ninterface TextBlockProps extends TextProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** Portable Text source. WINS over `html`, which is the derived cache. */\n content?: PortableTextDocument;\n}\n\nexport function Text({ html, content, align = \"left\", className = \"\" }: TextBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;\n\n // `content` wins when present — it is the source of truth and renders through React, which\n // escapes by construction. `html` remains the fallback so every block authored before\n // Portable Text existed renders exactly as it always did.\n if (content && content.content.length > 0) {\n return (\n <div className={cls}>\n <PortableText doc={content} />\n </div>\n );\n }\n\n return (\n <div\n className={cls}\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { ImageProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface ImageBlockProps extends ImageProps {\n className?: string;\n sizes?: string;\n}\n\nexport function Image({\n src,\n alt,\n width,\n height,\n caption,\n className = \"\",\n sizes,\n}: ImageBlockProps) {\n return (\n <figure className={cn(\"w-full\", className)}>\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n className=\"w-full h-auto rounded-md object-cover\"\n loading=\"lazy\"\n />\n {caption && (\n <figcaption className=\"mt-2 text-sm text-muted-foreground text-center italic\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n","import type { ButtonProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface ButtonBlockProps extends ButtonProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Button({\n text,\n href,\n variant = \"primary\",\n align = \"left\",\n className = \"\",\n}: ButtonBlockProps) {\n const baseStyles =\n \"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\";\n\n const variantStyles = {\n primary:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n };\n\n const alignClass = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n }[align];\n\n return (\n <a\n href={href}\n className={cn(baseStyles, variantStyles[variant], alignClass, \"px-4 py-2\", className)}\n >\n {text}\n </a>\n );\n}\n","import type { SpacerProps } from \"@bettercms-ai/types\";\n\ninterface SpacerBlockProps extends SpacerProps {\n className?: string;\n}\n\nexport function Spacer({ height = 32, className = \"\" }: SpacerBlockProps) {\n return (\n <div\n className={`w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`}\n style={{ height: `${height}px` }}\n aria-hidden=\"true\"\n >\n <span className=\"text-xs text-muted-foreground/40 select-none\">\n {height}px\n </span>\n </div>\n );\n}\n","import type { ColumnsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface ColumnsBlockProps extends ColumnsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Columns({\n columns,\n gap = 16,\n className = \"\",\n renderBlock,\n}: ColumnsBlockProps) {\n return (\n <div\n className={cn(\"grid w-full\", className)}\n style={{\n gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,\n gap: `${gap}px`,\n }}\n >\n {columns.map((columnBlocks, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-4 min-w-0\">\n {columnBlocks.map((block) => (\n <span key={block.id}>\n {renderBlock ? renderBlock(block) : null}\n </span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { VideoProps } from \"@bettercms-ai/types\";\nimport { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ninterface VideoBlockProps extends VideoProps {\n className?: string;\n}\n\nfunction getEmbedUrl(url: string): { embedUrl: string; provider: \"youtube\" | \"vimeo\" | \"unknown\" } {\n // YouTube\n const ytMatch = url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/\n );\n if (ytMatch) {\n return {\n embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,\n provider: \"youtube\",\n };\n }\n\n // Vimeo\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) {\n return {\n embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,\n provider: \"vimeo\",\n };\n }\n\n return { embedUrl: url, provider: \"unknown\" };\n}\n\nexport function Video({\n url,\n poster,\n autoplay = false,\n loop = false,\n className = \"\",\n}: VideoBlockProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const { embedUrl, provider } = getEmbedUrl(url);\n\n // If not a recognized embed, render a native video tag\n if (provider === \"unknown\") {\n return (\n <video\n src={url}\n poster={poster}\n autoPlay={autoplay}\n loop={loop}\n controls\n className={className}\n playsInline\n />\n );\n }\n\n return (\n <div className={cn(\"w-full overflow-hidden rounded-md bg-black aspect-video\", className)}>\n {!isLoaded && poster && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={poster}\n alt=\"Video thumbnail\"\n className=\"w-full h-full object-cover absolute inset-0\"\n />\n )}\n <iframe\n src={`${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n loading=\"lazy\"\n onLoad={() => setIsLoaded(true)}\n className=\"w-full h-full\"\n />\n </div>\n );\n}\n","import type { SectionProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface SectionBlockProps extends SectionProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Section({\n children,\n background,\n paddingY,\n maxWidth,\n className = \"\",\n renderBlock,\n}: SectionBlockProps) {\n const inner = (children ?? []).map((block) => (\n <span key={block.id}>{renderBlock ? renderBlock(block) : null}</span>\n ));\n return (\n <section\n className={cn(\"w-full\", className)}\n style={{ background, paddingTop: paddingY, paddingBottom: paddingY }}\n >\n {maxWidth ? <div style={{ maxWidth, marginInline: \"auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n","import type { NavbarProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface NavbarBlockProps extends NavbarProps {\n className?: string;\n}\n\nexport function Navbar({ logo, links, cta, className = \"\" }: NavbarBlockProps) {\n return (\n <nav className={cn(\"flex items-center gap-6 py-4\", className)}>\n {logo?.src ? (\n <a href={logo.href ?? \"/\"} className=\"shrink-0\">\n <img src={logo.src} alt={logo.alt} className=\"h-8 w-auto\" />\n </a>\n ) : null}\n <div className=\"flex flex-1 gap-5\">\n {(links ?? []).map((l, i) => (\n <a key={i} href={l.href} className=\"text-inherit no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n {cta ? (\n <a\n href={cta.href}\n className=\"rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline\"\n >\n {cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n","import type { FooterProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface FooterBlockProps extends FooterProps {\n className?: string;\n}\n\nexport function Footer({ columns, copyright, className = \"\" }: FooterBlockProps) {\n return (\n <footer className={cn(\"mt-12 border-t pt-8\", className)}>\n <div className=\"flex flex-wrap gap-12\">\n {(columns ?? []).map((col, i) => (\n <div key={i} className=\"flex flex-col gap-1.5\">\n {col.heading ? <h4 className=\"mb-2 font-semibold\">{col.heading}</h4> : null}\n {(col.links ?? []).map((l, j) => (\n <a key={j} href={l.href} className=\"text-neutral-500 no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n ))}\n </div>\n {copyright ? <p className=\"mt-6 text-sm text-neutral-500\">{copyright}</p> : null}\n </footer>\n );\n}\n","\"use client\";\n\nimport type { SliderProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface SliderBlockProps extends SliderProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Slider({ slides, className = \"\", renderBlock }: SliderBlockProps) {\n const [i, setI] = useState(0);\n const list = slides ?? [];\n const go = (n: number) => setI((n + list.length) % (list.length || 1));\n const btn =\n \"absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer\";\n return (\n <div className={cn(\"relative w-full\", className)}>\n {list.map((s, idx) => (\n <div key={s.id} hidden={idx !== i} className=\"flex flex-col gap-4\">\n {(s.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n <button type=\"button\" aria-label=\"Previous\" onClick={() => go(i - 1)} className={cn(btn, \"left-2\")}>\n ‹\n </button>\n <button type=\"button\" aria-label=\"Next\" onClick={() => go(i + 1)} className={cn(btn, \"right-2\")}>\n ›\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport type { TabsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface TabsBlockProps extends TabsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Tabs({ tabs, className = \"\", renderBlock }: TabsBlockProps) {\n const [active, setActive] = useState(0);\n const list = tabs ?? [];\n return (\n <div className={cn(\"w-full\", className)}>\n <div role=\"tablist\" className=\"mb-4 flex gap-1 border-b\">\n {list.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n className={cn(\n \"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2\",\n idx === active && \"border-black font-semibold\",\n )}\n >\n {t.label}\n </button>\n ))}\n </div>\n {list.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" hidden={idx !== active} className=\"flex flex-col gap-4\">\n {(t.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { ComponentProps as BcmsComponentProps, FormProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\nconst box =\n \"rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500\";\n\n/**\n * Builder-preview placeholder for a `component` instance. Live instance resolution\n * (definition lookup + override merge) happens server-side in the delivery renderer\n * and in the page-builder, which pass a resolver — the standalone library preview\n * just shows what's referenced.\n */\nexport function ComponentInstance({\n componentId,\n className = \"\",\n}: BcmsComponentProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Component instance: <code>{componentId}</code>\n </div>\n );\n}\n\n/** Builder-preview placeholder for a `form` block (rendered live by @bettercms-ai/next). */\nexport function FormPlaceholder({ formId, className = \"\" }: FormProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Form: <code>{formId}</code>\n </div>\n );\n}\n","import type { ContentBlock, BlockType } from \"@bettercms-ai/types\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Text } from \"./blocks/Text\";\nimport { Image } from \"./blocks/Image\";\nimport { Button } from \"./blocks/Button\";\nimport { Spacer } from \"./blocks/Spacer\";\nimport { Columns } from \"./blocks/Columns\";\nimport { Video } from \"./blocks/Video\";\nimport { Section } from \"./blocks/Section\";\nimport { Navbar } from \"./blocks/Navbar\";\nimport { Footer } from \"./blocks/Footer\";\nimport { Slider } from \"./blocks/Slider\";\nimport { Tabs } from \"./blocks/Tabs\";\nimport { ComponentInstance, FormPlaceholder } from \"./blocks/Placeholders\";\n\n/**\n * Block registry entry — maps a block type to its React component\n * and metadata for the block library UI.\n */\nexport interface BlockRegistryEntry {\n component: React.ComponentType<Record<string, unknown>>;\n label: string;\n icon: string;\n}\n\nexport const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry> = {\n heading: {\n component: Heading as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Heading\",\n icon: \"type\",\n },\n text: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Text\",\n icon: \"align-left\",\n },\n // Same payload, same renderer — deliberately. `richtext` differs from `text` only in how\n // an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML\n // string, so duplicating the component would be two things to keep in sync for no gain.\n richtext: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Rich text\",\n icon: \"pilcrow\",\n },\n image: {\n component: Image as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Image\",\n icon: \"image\",\n },\n button: {\n component: Button as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Button\",\n icon: \"square\",\n },\n spacer: {\n component: Spacer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Spacer\",\n icon: \"minus\",\n },\n columns: {\n component: Columns as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Columns\",\n icon: \"columns\",\n },\n video: {\n component: Video as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Video\",\n icon: \"play\",\n },\n section: {\n component: Section as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Section\",\n icon: \"layout\",\n },\n navbar: {\n component: Navbar as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Navbar\",\n icon: \"menu\",\n },\n footer: {\n component: Footer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Footer\",\n icon: \"panel-bottom\",\n },\n slider: {\n component: Slider as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Slider\",\n icon: \"gallery-horizontal\",\n },\n tabs: {\n component: Tabs as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Tabs\",\n icon: \"folder\",\n },\n component: {\n component: ComponentInstance as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Component\",\n icon: \"component\",\n },\n form: {\n component: FormPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Form\",\n icon: \"clipboard-list\",\n },\n} as const;\n\nexport const BLOCK_TYPES = Object.keys(BLOCK_REGISTRY) as BlockType[];\n\nexport type { ContentBlock, BlockType };\n","import type { CSSProperties } from \"react\";\nimport { blockStyleToCss, type BlockStyle } from \"@bettercms-ai/types\";\n\n/**\n * FLO-264 — React-typed wrapper over the shared, framework-agnostic\n * `blockStyleToCss` in @bettercms-ai/types. One mapping, three consumers (this UI\n * renderer, the @bettercms-ai/next adapter, and — in string form — the hosted\n * render-page.ts).\n */\nexport function blockStyleToReactStyle(style: BlockStyle | undefined): CSSProperties | undefined {\n return blockStyleToCss(style) as CSSProperties | undefined;\n}\n","import type { ContentBlock } from \"@bettercms-ai/types\";\nimport { BLOCK_REGISTRY, type BlockRegistryEntry } from \"./registry\";\nimport { blockStyleToReactStyle } from \"./blockStyle\";\nimport React from \"react\";\n\n// Container blocks hold nested blocks and need the `renderBlock` callback threaded\n// so their children render recursively.\nconst CONTAINER_TYPES = new Set([\"columns\", \"section\", \"slider\", \"tabs\"]);\n\ninterface BlockRendererProps {\n block: ContentBlock;\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nfunction resolveBlock(block: ContentBlock, className?: string): React.ReactNode {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? \"\"}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n const Component = entry.component;\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = <Component {...block.props} className={className} />;\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\nexport function BlockRenderer({\n block,\n className = \"\",\n renderBlock,\n}: BlockRendererProps) {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n // Container blocks (columns/section/slider/tabs) hold nested blocks, so thread the\n // renderBlock callback. Standalone usage (no BlocksRenderer above) falls back to\n // resolveBlock so one level of nesting still renders.\n const Component = entry.component;\n const containerProps = CONTAINER_TYPES.has(block.type)\n ? { renderBlock: renderBlock ?? resolveBlock }\n : {};\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = <Component {...block.props} className={className} {...containerProps} />;\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\ninterface BlocksRendererProps {\n blocks: ContentBlock[];\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function BlocksRenderer({\n blocks,\n className = \"\",\n renderBlock,\n}: BlocksRendererProps) {\n if (blocks.length === 0) return null;\n\n return (\n <div className={className}>\n {blocks.map((block) => (\n <BlockRenderer\n key={block.id}\n block={block}\n renderBlock={renderBlock}\n />\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsx","jsx","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
|