@bettercms-ai/ui 0.3.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/README.md +96 -0
- package/dist/Video-Dd3TLlXA.d.ts +45 -0
- package/dist/blocks.d.ts +48 -0
- package/dist/blocks.js +344 -0
- package/dist/blocks.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +514 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +16 -0
- package/dist/registry.js +407 -0
- package/dist/registry.js.map +1 -0
- package/dist/renderer.d.ts +18 -0
- package/dist/renderer.js +504 -0
- package/dist/renderer.js.map +1 -0
- package/dist/resolve.d.ts +40 -0
- package/dist/resolve.js +58 -0
- package/dist/resolve.js.map +1 -0
- package/package.json +79 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/blocks/Heading.tsx
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
function Heading({
|
|
6
|
+
text,
|
|
7
|
+
level = 2,
|
|
8
|
+
align = "left",
|
|
9
|
+
className = ""
|
|
10
|
+
}) {
|
|
11
|
+
const alignClass = {
|
|
12
|
+
left: "text-left",
|
|
13
|
+
center: "text-center",
|
|
14
|
+
right: "text-right"
|
|
15
|
+
}[align];
|
|
16
|
+
const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;
|
|
17
|
+
switch (level) {
|
|
18
|
+
case 1:
|
|
19
|
+
return /* @__PURE__ */ jsx("h1", { className: classNames, children: text });
|
|
20
|
+
case 2:
|
|
21
|
+
return /* @__PURE__ */ jsx("h2", { className: classNames, children: text });
|
|
22
|
+
case 3:
|
|
23
|
+
return /* @__PURE__ */ jsx("h3", { className: classNames, children: text });
|
|
24
|
+
default:
|
|
25
|
+
return /* @__PURE__ */ jsx("h2", { className: classNames, children: text });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/blocks/Text.tsx
|
|
30
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
31
|
+
function Text({ html, align = "left", className = "" }) {
|
|
32
|
+
const alignClass = {
|
|
33
|
+
left: "text-left",
|
|
34
|
+
center: "text-center",
|
|
35
|
+
right: "text-right"
|
|
36
|
+
}[align];
|
|
37
|
+
return /* @__PURE__ */ jsx2(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
className: `text-muted-foreground leading-relaxed ${alignClass} ${className}`,
|
|
41
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/lib/utils.ts
|
|
47
|
+
import { clsx } from "clsx";
|
|
48
|
+
import { twMerge } from "tailwind-merge";
|
|
49
|
+
function cn(...inputs) {
|
|
50
|
+
return twMerge(clsx(inputs));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/blocks/Image.tsx
|
|
54
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
55
|
+
function Image({
|
|
56
|
+
src,
|
|
57
|
+
alt,
|
|
58
|
+
width,
|
|
59
|
+
height,
|
|
60
|
+
caption,
|
|
61
|
+
className = "",
|
|
62
|
+
sizes
|
|
63
|
+
}) {
|
|
64
|
+
return /* @__PURE__ */ jsxs("figure", { className: cn("w-full", className), children: [
|
|
65
|
+
/* @__PURE__ */ jsx3(
|
|
66
|
+
"img",
|
|
67
|
+
{
|
|
68
|
+
src,
|
|
69
|
+
alt,
|
|
70
|
+
width,
|
|
71
|
+
height,
|
|
72
|
+
sizes,
|
|
73
|
+
className: "w-full h-auto rounded-md object-cover",
|
|
74
|
+
loading: "lazy"
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
caption && /* @__PURE__ */ jsx3("figcaption", { className: "mt-2 text-sm text-muted-foreground text-center italic", children: caption })
|
|
78
|
+
] });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/blocks/Button.tsx
|
|
82
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
83
|
+
function Button({
|
|
84
|
+
text,
|
|
85
|
+
href,
|
|
86
|
+
variant = "primary",
|
|
87
|
+
align = "left",
|
|
88
|
+
className = ""
|
|
89
|
+
}) {
|
|
90
|
+
const baseStyles = "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";
|
|
91
|
+
const variantStyles = {
|
|
92
|
+
primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
93
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80"
|
|
94
|
+
};
|
|
95
|
+
const alignClass = {
|
|
96
|
+
left: "justify-start",
|
|
97
|
+
center: "justify-center",
|
|
98
|
+
right: "justify-end"
|
|
99
|
+
}[align];
|
|
100
|
+
return /* @__PURE__ */ jsx4(
|
|
101
|
+
"a",
|
|
102
|
+
{
|
|
103
|
+
href,
|
|
104
|
+
className: cn(baseStyles, variantStyles[variant], alignClass, "px-4 py-2", className),
|
|
105
|
+
children: text
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/blocks/Spacer.tsx
|
|
111
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
112
|
+
function Spacer({ height = 32, className = "" }) {
|
|
113
|
+
return /* @__PURE__ */ jsx5(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
className: `w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`,
|
|
117
|
+
style: { height: `${height}px` },
|
|
118
|
+
"aria-hidden": "true",
|
|
119
|
+
children: /* @__PURE__ */ jsxs2("span", { className: "text-xs text-muted-foreground/40 select-none", children: [
|
|
120
|
+
height,
|
|
121
|
+
"px"
|
|
122
|
+
] })
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/blocks/Columns.tsx
|
|
128
|
+
import "react";
|
|
129
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
130
|
+
function Columns({
|
|
131
|
+
columns,
|
|
132
|
+
gap = 16,
|
|
133
|
+
className = "",
|
|
134
|
+
renderBlock
|
|
135
|
+
}) {
|
|
136
|
+
return /* @__PURE__ */ jsx6(
|
|
137
|
+
"div",
|
|
138
|
+
{
|
|
139
|
+
className: cn("grid w-full", className),
|
|
140
|
+
style: {
|
|
141
|
+
gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,
|
|
142
|
+
gap: `${gap}px`
|
|
143
|
+
},
|
|
144
|
+
children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */ jsx6("div", { className: "flex flex-col gap-4 min-w-0", children: columnBlocks.map((block) => /* @__PURE__ */ jsx6("span", { children: renderBlock ? renderBlock(block) : null }, block.id)) }, colIndex))
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/blocks/Video.tsx
|
|
150
|
+
import { useState } from "react";
|
|
151
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
152
|
+
function getEmbedUrl(url) {
|
|
153
|
+
const ytMatch = url.match(
|
|
154
|
+
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
|
|
155
|
+
);
|
|
156
|
+
if (ytMatch) {
|
|
157
|
+
return {
|
|
158
|
+
embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,
|
|
159
|
+
provider: "youtube"
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
|
|
163
|
+
if (vimeoMatch) {
|
|
164
|
+
return {
|
|
165
|
+
embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,
|
|
166
|
+
provider: "vimeo"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { embedUrl: url, provider: "unknown" };
|
|
170
|
+
}
|
|
171
|
+
function Video({
|
|
172
|
+
url,
|
|
173
|
+
poster,
|
|
174
|
+
autoplay = false,
|
|
175
|
+
loop = false,
|
|
176
|
+
className = ""
|
|
177
|
+
}) {
|
|
178
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
179
|
+
const { embedUrl, provider } = getEmbedUrl(url);
|
|
180
|
+
if (provider === "unknown") {
|
|
181
|
+
return /* @__PURE__ */ jsx7(
|
|
182
|
+
"video",
|
|
183
|
+
{
|
|
184
|
+
src: url,
|
|
185
|
+
poster,
|
|
186
|
+
autoPlay: autoplay,
|
|
187
|
+
loop,
|
|
188
|
+
controls: true,
|
|
189
|
+
className,
|
|
190
|
+
playsInline: true
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
|
|
195
|
+
!isLoaded && poster && // eslint-disable-next-line @next/next/no-img-element
|
|
196
|
+
/* @__PURE__ */ jsx7(
|
|
197
|
+
"img",
|
|
198
|
+
{
|
|
199
|
+
src: poster,
|
|
200
|
+
alt: "Video thumbnail",
|
|
201
|
+
className: "w-full h-full object-cover absolute inset-0"
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx7(
|
|
205
|
+
"iframe",
|
|
206
|
+
{
|
|
207
|
+
src: `${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`,
|
|
208
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
209
|
+
allowFullScreen: true,
|
|
210
|
+
loading: "lazy",
|
|
211
|
+
onLoad: () => setIsLoaded(true),
|
|
212
|
+
className: "w-full h-full"
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/blocks/Section.tsx
|
|
219
|
+
import "react";
|
|
220
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
221
|
+
function Section({
|
|
222
|
+
children,
|
|
223
|
+
background,
|
|
224
|
+
paddingY,
|
|
225
|
+
maxWidth,
|
|
226
|
+
className = "",
|
|
227
|
+
renderBlock
|
|
228
|
+
}) {
|
|
229
|
+
const inner = (children ?? []).map((block) => /* @__PURE__ */ jsx8("span", { children: renderBlock ? renderBlock(block) : null }, block.id));
|
|
230
|
+
return /* @__PURE__ */ jsx8(
|
|
231
|
+
"section",
|
|
232
|
+
{
|
|
233
|
+
className: cn("w-full", className),
|
|
234
|
+
style: { background, paddingTop: paddingY, paddingBottom: paddingY },
|
|
235
|
+
children: maxWidth ? /* @__PURE__ */ jsx8("div", { style: { maxWidth, marginInline: "auto" }, children: inner }) : inner
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/blocks/Navbar.tsx
|
|
241
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
242
|
+
function Navbar({ logo, links, cta, className = "" }) {
|
|
243
|
+
return /* @__PURE__ */ jsxs4("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
|
|
244
|
+
logo?.src ? /* @__PURE__ */ jsx9("a", { href: logo.href ?? "/", className: "shrink-0", children: /* @__PURE__ */ jsx9("img", { src: logo.src, alt: logo.alt, className: "h-8 w-auto" }) }) : null,
|
|
245
|
+
/* @__PURE__ */ jsx9("div", { className: "flex flex-1 gap-5", children: (links ?? []).map((l, i) => /* @__PURE__ */ jsx9("a", { href: l.href, className: "text-inherit no-underline", children: l.label }, i)) }),
|
|
246
|
+
cta ? /* @__PURE__ */ jsx9(
|
|
247
|
+
"a",
|
|
248
|
+
{
|
|
249
|
+
href: cta.href,
|
|
250
|
+
className: "rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline",
|
|
251
|
+
children: cta.text
|
|
252
|
+
}
|
|
253
|
+
) : null
|
|
254
|
+
] });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/blocks/Footer.tsx
|
|
258
|
+
import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
259
|
+
function Footer({ columns, copyright, className = "" }) {
|
|
260
|
+
return /* @__PURE__ */ jsxs5("footer", { className: cn("mt-12 border-t pt-8", className), children: [
|
|
261
|
+
/* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1.5", children: [
|
|
262
|
+
col.heading ? /* @__PURE__ */ jsx10("h4", { className: "mb-2 font-semibold", children: col.heading }) : null,
|
|
263
|
+
(col.links ?? []).map((l, j) => /* @__PURE__ */ jsx10("a", { href: l.href, className: "text-neutral-500 no-underline", children: l.label }, j))
|
|
264
|
+
] }, i)) }),
|
|
265
|
+
copyright ? /* @__PURE__ */ jsx10("p", { className: "mt-6 text-sm text-neutral-500", children: copyright }) : null
|
|
266
|
+
] });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/blocks/Slider.tsx
|
|
270
|
+
import { useState as useState2 } from "react";
|
|
271
|
+
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
272
|
+
function Slider({ slides, className = "", renderBlock }) {
|
|
273
|
+
const [i, setI] = useState2(0);
|
|
274
|
+
const list = slides ?? [];
|
|
275
|
+
const go = (n) => setI((n + list.length) % (list.length || 1));
|
|
276
|
+
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
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn("relative w-full", className), children: [
|
|
278
|
+
list.map((s, idx) => /* @__PURE__ */ jsx11("div", { hidden: idx !== i, className: "flex flex-col gap-4", children: (s.children ?? []).map((b) => /* @__PURE__ */ jsx11("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, s.id)),
|
|
279
|
+
/* @__PURE__ */ jsx11("button", { type: "button", "aria-label": "Previous", onClick: () => go(i - 1), className: cn(btn, "left-2"), children: "\u2039" }),
|
|
280
|
+
/* @__PURE__ */ jsx11("button", { type: "button", "aria-label": "Next", onClick: () => go(i + 1), className: cn(btn, "right-2"), children: "\u203A" })
|
|
281
|
+
] });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/blocks/Tabs.tsx
|
|
285
|
+
import { useState as useState3 } from "react";
|
|
286
|
+
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
287
|
+
function Tabs({ tabs, className = "", renderBlock }) {
|
|
288
|
+
const [active, setActive] = useState3(0);
|
|
289
|
+
const list = tabs ?? [];
|
|
290
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("w-full", className), children: [
|
|
291
|
+
/* @__PURE__ */ jsx12("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx12(
|
|
292
|
+
"button",
|
|
293
|
+
{
|
|
294
|
+
role: "tab",
|
|
295
|
+
type: "button",
|
|
296
|
+
"aria-selected": idx === active,
|
|
297
|
+
onClick: () => setActive(idx),
|
|
298
|
+
className: cn(
|
|
299
|
+
"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2",
|
|
300
|
+
idx === active && "border-black font-semibold"
|
|
301
|
+
),
|
|
302
|
+
children: t.label
|
|
303
|
+
},
|
|
304
|
+
t.id
|
|
305
|
+
)) }),
|
|
306
|
+
list.map((t, idx) => /* @__PURE__ */ jsx12("div", { role: "tabpanel", hidden: idx !== active, className: "flex flex-col gap-4", children: (t.children ?? []).map((b) => /* @__PURE__ */ jsx12("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, t.id))
|
|
307
|
+
] });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/blocks/Placeholders.tsx
|
|
311
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
312
|
+
var box = "rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500";
|
|
313
|
+
function ComponentInstance({
|
|
314
|
+
componentId,
|
|
315
|
+
className = ""
|
|
316
|
+
}) {
|
|
317
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
|
|
318
|
+
"Component instance: ",
|
|
319
|
+
/* @__PURE__ */ jsx13("code", { children: componentId })
|
|
320
|
+
] });
|
|
321
|
+
}
|
|
322
|
+
function FormPlaceholder({ formId, className = "" }) {
|
|
323
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
|
|
324
|
+
"Form: ",
|
|
325
|
+
/* @__PURE__ */ jsx13("code", { children: formId })
|
|
326
|
+
] });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/registry.ts
|
|
330
|
+
var BLOCK_REGISTRY = {
|
|
331
|
+
heading: {
|
|
332
|
+
component: Heading,
|
|
333
|
+
label: "Heading",
|
|
334
|
+
icon: "type"
|
|
335
|
+
},
|
|
336
|
+
text: {
|
|
337
|
+
component: Text,
|
|
338
|
+
label: "Text",
|
|
339
|
+
icon: "align-left"
|
|
340
|
+
},
|
|
341
|
+
image: {
|
|
342
|
+
component: Image,
|
|
343
|
+
label: "Image",
|
|
344
|
+
icon: "image"
|
|
345
|
+
},
|
|
346
|
+
button: {
|
|
347
|
+
component: Button,
|
|
348
|
+
label: "Button",
|
|
349
|
+
icon: "square"
|
|
350
|
+
},
|
|
351
|
+
spacer: {
|
|
352
|
+
component: Spacer,
|
|
353
|
+
label: "Spacer",
|
|
354
|
+
icon: "minus"
|
|
355
|
+
},
|
|
356
|
+
columns: {
|
|
357
|
+
component: Columns,
|
|
358
|
+
label: "Columns",
|
|
359
|
+
icon: "columns"
|
|
360
|
+
},
|
|
361
|
+
video: {
|
|
362
|
+
component: Video,
|
|
363
|
+
label: "Video",
|
|
364
|
+
icon: "play"
|
|
365
|
+
},
|
|
366
|
+
section: {
|
|
367
|
+
component: Section,
|
|
368
|
+
label: "Section",
|
|
369
|
+
icon: "layout"
|
|
370
|
+
},
|
|
371
|
+
navbar: {
|
|
372
|
+
component: Navbar,
|
|
373
|
+
label: "Navbar",
|
|
374
|
+
icon: "menu"
|
|
375
|
+
},
|
|
376
|
+
footer: {
|
|
377
|
+
component: Footer,
|
|
378
|
+
label: "Footer",
|
|
379
|
+
icon: "panel-bottom"
|
|
380
|
+
},
|
|
381
|
+
slider: {
|
|
382
|
+
component: Slider,
|
|
383
|
+
label: "Slider",
|
|
384
|
+
icon: "gallery-horizontal"
|
|
385
|
+
},
|
|
386
|
+
tabs: {
|
|
387
|
+
component: Tabs,
|
|
388
|
+
label: "Tabs",
|
|
389
|
+
icon: "folder"
|
|
390
|
+
},
|
|
391
|
+
component: {
|
|
392
|
+
component: ComponentInstance,
|
|
393
|
+
label: "Component",
|
|
394
|
+
icon: "component"
|
|
395
|
+
},
|
|
396
|
+
form: {
|
|
397
|
+
component: FormPlaceholder,
|
|
398
|
+
label: "Form",
|
|
399
|
+
icon: "clipboard-list"
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
var BLOCK_TYPES = Object.keys(BLOCK_REGISTRY);
|
|
403
|
+
|
|
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
|
+
// src/blockStyle.ts
|
|
434
|
+
function blockStyleToReactStyle(style) {
|
|
435
|
+
return blockStyleToCss(style);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/renderer.tsx
|
|
439
|
+
import "react";
|
|
440
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
441
|
+
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["columns", "section", "slider", "tabs"]);
|
|
442
|
+
function resolveBlock(block, className) {
|
|
443
|
+
const entry = BLOCK_REGISTRY[block.type];
|
|
444
|
+
if (!entry) {
|
|
445
|
+
return /* @__PURE__ */ jsxs9(
|
|
446
|
+
"div",
|
|
447
|
+
{
|
|
448
|
+
className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? ""}`,
|
|
449
|
+
children: [
|
|
450
|
+
"Unknown block type: ",
|
|
451
|
+
/* @__PURE__ */ jsx14("code", { children: block.type })
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
const Component = entry.component;
|
|
457
|
+
const blockStyle = blockStyleToReactStyle(block.style);
|
|
458
|
+
const el = /* @__PURE__ */ jsx14(Component, { ...block.props, className });
|
|
459
|
+
return blockStyle ? /* @__PURE__ */ jsx14("div", { style: blockStyle, children: el }) : el;
|
|
460
|
+
}
|
|
461
|
+
function BlockRenderer({
|
|
462
|
+
block,
|
|
463
|
+
className = "",
|
|
464
|
+
renderBlock
|
|
465
|
+
}) {
|
|
466
|
+
const entry = BLOCK_REGISTRY[block.type];
|
|
467
|
+
if (!entry) {
|
|
468
|
+
return /* @__PURE__ */ jsxs9(
|
|
469
|
+
"div",
|
|
470
|
+
{
|
|
471
|
+
className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`,
|
|
472
|
+
children: [
|
|
473
|
+
"Unknown block type: ",
|
|
474
|
+
/* @__PURE__ */ jsx14("code", { children: block.type })
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
const Component = entry.component;
|
|
480
|
+
const containerProps = CONTAINER_TYPES.has(block.type) ? { renderBlock: renderBlock ?? resolveBlock } : {};
|
|
481
|
+
const blockStyle = blockStyleToReactStyle(block.style);
|
|
482
|
+
const el = /* @__PURE__ */ jsx14(Component, { ...block.props, className, ...containerProps });
|
|
483
|
+
return blockStyle ? /* @__PURE__ */ jsx14("div", { style: blockStyle, children: el }) : el;
|
|
484
|
+
}
|
|
485
|
+
function BlocksRenderer({
|
|
486
|
+
blocks,
|
|
487
|
+
className = "",
|
|
488
|
+
renderBlock
|
|
489
|
+
}) {
|
|
490
|
+
if (blocks.length === 0) return null;
|
|
491
|
+
return /* @__PURE__ */ jsx14("div", { className, children: blocks.map((block) => /* @__PURE__ */ jsx14(
|
|
492
|
+
BlockRenderer,
|
|
493
|
+
{
|
|
494
|
+
block,
|
|
495
|
+
renderBlock
|
|
496
|
+
},
|
|
497
|
+
block.id
|
|
498
|
+
)) });
|
|
499
|
+
}
|
|
500
|
+
export {
|
|
501
|
+
BLOCK_REGISTRY,
|
|
502
|
+
BLOCK_TYPES,
|
|
503
|
+
BlockRenderer,
|
|
504
|
+
BlocksRenderer,
|
|
505
|
+
Button,
|
|
506
|
+
Columns,
|
|
507
|
+
Heading,
|
|
508
|
+
Image,
|
|
509
|
+
Spacer,
|
|
510
|
+
Text,
|
|
511
|
+
Video,
|
|
512
|
+
cn
|
|
513
|
+
};
|
|
514
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BlockType } from '@bettercms-ai/types';
|
|
2
|
+
export { BlockType, ContentBlock } from '@bettercms-ai/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Block registry entry — maps a block type to its React component
|
|
6
|
+
* and metadata for the block library UI.
|
|
7
|
+
*/
|
|
8
|
+
interface BlockRegistryEntry {
|
|
9
|
+
component: React.ComponentType<Record<string, unknown>>;
|
|
10
|
+
label: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
}
|
|
13
|
+
declare const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry>;
|
|
14
|
+
declare const BLOCK_TYPES: BlockType[];
|
|
15
|
+
|
|
16
|
+
export { BLOCK_REGISTRY, BLOCK_TYPES, type BlockRegistryEntry };
|