@arbidocs/blocks 0.3.111 → 0.3.113
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/index.cjs +1548 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +371 -31
- package/dist/index.d.ts +371 -31
- package/dist/index.js +1529 -116
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/studio/studio.css +19 -18
package/dist/index.cjs
CHANGED
|
@@ -809,7 +809,7 @@ function EmptyState({
|
|
|
809
809
|
"data-testid": testId,
|
|
810
810
|
children: [
|
|
811
811
|
Icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 flex size-11 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-5" }) }),
|
|
812
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-display text-base font-semibold text-
|
|
812
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-display text-base font-semibold text-foreground", children: title }),
|
|
813
813
|
description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: description }),
|
|
814
814
|
action && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: action })
|
|
815
815
|
]
|
|
@@ -1177,10 +1177,10 @@ function GridView({
|
|
|
1177
1177
|
const BORDER = 2;
|
|
1178
1178
|
const PREFERRED_PAD = 16;
|
|
1179
1179
|
const padTotal = Math.max(0, Math.min(PREFERRED_PAD, rowHeight - BORDER - LINE_PX));
|
|
1180
|
-
const
|
|
1180
|
+
const lines2 = Math.max(1, Math.floor((rowHeight - BORDER - padTotal) / LINE_PX));
|
|
1181
1181
|
const vars = style;
|
|
1182
1182
|
vars["--abstract-pad-y"] = `${padTotal / 2}px`;
|
|
1183
|
-
vars["--abstract-max-h"] = `${
|
|
1183
|
+
vars["--abstract-max-h"] = `${lines2 * LINE_PX + padTotal + BORDER}px`;
|
|
1184
1184
|
}
|
|
1185
1185
|
return style;
|
|
1186
1186
|
}, [height, rowHeight]);
|
|
@@ -1302,8 +1302,8 @@ function PageHeader({
|
|
|
1302
1302
|
"data-testid": testId,
|
|
1303
1303
|
children: [
|
|
1304
1304
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
1305
|
-
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "
|
|
1306
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-display text-2xl font-semibold tracking-tight text-
|
|
1305
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
|
|
1306
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-display text-2xl font-semibold tracking-tight text-foreground", children: title }),
|
|
1307
1307
|
description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 max-w-2xl text-sm text-muted-foreground", children: description })
|
|
1308
1308
|
] }),
|
|
1309
1309
|
actions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions })
|
|
@@ -1317,16 +1317,26 @@ function SectionHeading({
|
|
|
1317
1317
|
lead,
|
|
1318
1318
|
align = "left",
|
|
1319
1319
|
className,
|
|
1320
|
-
invert
|
|
1320
|
+
invert,
|
|
1321
|
+
eyebrowClassName
|
|
1321
1322
|
}) {
|
|
1322
1323
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: react.cn("max-w-3xl", align === "center" && "mx-auto text-center", className), children: [
|
|
1323
|
-
eyebrow && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1324
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1325
|
+
"p",
|
|
1326
|
+
{
|
|
1327
|
+
className: react.cn(
|
|
1328
|
+
"mb-3 text-xs font-semibold uppercase tracking-wider",
|
|
1329
|
+
eyebrowClassName ?? (invert ? "text-background/60" : "text-muted-foreground")
|
|
1330
|
+
),
|
|
1331
|
+
children: eyebrow
|
|
1332
|
+
}
|
|
1333
|
+
),
|
|
1324
1334
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1325
1335
|
"h2",
|
|
1326
1336
|
{
|
|
1327
1337
|
className: react.cn(
|
|
1328
1338
|
"font-display text-3xl font-semibold tracking-tight text-balance sm:text-4xl",
|
|
1329
|
-
invert ? "text-
|
|
1339
|
+
invert ? "text-background" : "text-foreground"
|
|
1330
1340
|
),
|
|
1331
1341
|
children: title
|
|
1332
1342
|
}
|
|
@@ -1336,7 +1346,7 @@ function SectionHeading({
|
|
|
1336
1346
|
{
|
|
1337
1347
|
className: react.cn(
|
|
1338
1348
|
"mt-4 text-lg leading-relaxed",
|
|
1339
|
-
invert ? "text-
|
|
1349
|
+
invert ? "text-background/70" : "text-muted-foreground"
|
|
1340
1350
|
),
|
|
1341
1351
|
children: lead
|
|
1342
1352
|
}
|
|
@@ -1388,15 +1398,15 @@ function MetricCard({
|
|
|
1388
1398
|
icon: Icon,
|
|
1389
1399
|
hint,
|
|
1390
1400
|
delta,
|
|
1391
|
-
accent = "
|
|
1401
|
+
accent = "default",
|
|
1392
1402
|
testId
|
|
1393
1403
|
}) {
|
|
1394
|
-
const
|
|
1404
|
+
const accentStyle = accent && accent !== "default" ? { color: `var(--color-${accent})` } : void 0;
|
|
1395
1405
|
const deltaGood = delta?.good ?? delta?.direction === "up";
|
|
1396
1406
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Card, { variant: "elevated", className: "card-hover rounded-xl p-5", "data-testid": testId, children: [
|
|
1397
1407
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
1398
1408
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: label }),
|
|
1399
|
-
Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { className:
|
|
1409
|
+
Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4 shrink-0", style: accentStyle })
|
|
1400
1410
|
] }),
|
|
1401
1411
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1402
1412
|
"p",
|
|
@@ -1424,6 +1434,54 @@ function MetricCard({
|
|
|
1424
1434
|
] })
|
|
1425
1435
|
] });
|
|
1426
1436
|
}
|
|
1437
|
+
var ASSET_REF_PREFIX = "asset:";
|
|
1438
|
+
function asAssetRef(docId) {
|
|
1439
|
+
return `${ASSET_REF_PREFIX}${docId}`;
|
|
1440
|
+
}
|
|
1441
|
+
function assetRefId(src) {
|
|
1442
|
+
return src.slice(ASSET_REF_PREFIX.length);
|
|
1443
|
+
}
|
|
1444
|
+
var AssetResolverContext = react$1.createContext(false);
|
|
1445
|
+
function AssetResolverProvider({ children }) {
|
|
1446
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AssetResolverContext.Provider, { value: true, children });
|
|
1447
|
+
}
|
|
1448
|
+
function useAssetResolverActive() {
|
|
1449
|
+
return react$1.useContext(AssetResolverContext);
|
|
1450
|
+
}
|
|
1451
|
+
function isAssetRef(src) {
|
|
1452
|
+
return !!src && src.startsWith(ASSET_REF_PREFIX);
|
|
1453
|
+
}
|
|
1454
|
+
function useAssetImage(docId) {
|
|
1455
|
+
const arbi = react.useArbi();
|
|
1456
|
+
const { data: thumbs } = react.useThumbnails([docId]);
|
|
1457
|
+
const thumb = thumbs?.[docId];
|
|
1458
|
+
const { data: objectUrl } = reactQuery.useQuery({
|
|
1459
|
+
queryKey: ["arbi", "asset-image", docId],
|
|
1460
|
+
queryFn: async () => {
|
|
1461
|
+
const res = await arbi.documents.download(docId);
|
|
1462
|
+
const blob = await res.blob();
|
|
1463
|
+
return URL.createObjectURL(blob);
|
|
1464
|
+
},
|
|
1465
|
+
// Only download when the thumbnail is genuinely absent (not merely loading).
|
|
1466
|
+
enabled: !!docId && thumbs != null && !thumb && arbi.hasWorkspace,
|
|
1467
|
+
staleTime: 10 * 6e4
|
|
1468
|
+
});
|
|
1469
|
+
react$1.useEffect(() => {
|
|
1470
|
+
return () => {
|
|
1471
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
1472
|
+
};
|
|
1473
|
+
}, [objectUrl]);
|
|
1474
|
+
return thumb ?? objectUrl;
|
|
1475
|
+
}
|
|
1476
|
+
function AssetThumb({
|
|
1477
|
+
docId,
|
|
1478
|
+
alt,
|
|
1479
|
+
className,
|
|
1480
|
+
testId
|
|
1481
|
+
}) {
|
|
1482
|
+
const src = useAssetImage(docId);
|
|
1483
|
+
return /* @__PURE__ */ jsxRuntime.jsx("img", { src: src ?? void 0, alt: alt ?? "", className, "data-testid": testId });
|
|
1484
|
+
}
|
|
1427
1485
|
var ASPECT = {
|
|
1428
1486
|
"16:9": "aspect-video",
|
|
1429
1487
|
"4:3": "aspect-[4/3]",
|
|
@@ -1442,11 +1500,28 @@ function BlockImage({
|
|
|
1442
1500
|
alt = "",
|
|
1443
1501
|
aspect = "16:9",
|
|
1444
1502
|
rounded = "lg",
|
|
1503
|
+
framed,
|
|
1445
1504
|
className,
|
|
1446
1505
|
testId
|
|
1447
1506
|
}) {
|
|
1448
|
-
const shape = react.cn(
|
|
1449
|
-
|
|
1507
|
+
const shape = react.cn(
|
|
1508
|
+
ASPECT[aspect] ?? ASPECT["16:9"],
|
|
1509
|
+
ROUNDED[rounded] ?? ROUNDED.lg,
|
|
1510
|
+
framed && "ring-1 ring-border shadow-xl"
|
|
1511
|
+
);
|
|
1512
|
+
const canResolve = useAssetResolverActive();
|
|
1513
|
+
if (canResolve && isAssetRef(src)) {
|
|
1514
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1515
|
+
AssetThumb,
|
|
1516
|
+
{
|
|
1517
|
+
docId: assetRefId(src),
|
|
1518
|
+
alt,
|
|
1519
|
+
className: react.cn("w-full object-cover", shape, className),
|
|
1520
|
+
testId
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
if (src && !isAssetRef(src)) {
|
|
1450
1525
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1451
1526
|
"img",
|
|
1452
1527
|
{
|
|
@@ -1478,38 +1553,82 @@ var TONE_SURFACE = {
|
|
|
1478
1553
|
muted: "bg-muted text-foreground",
|
|
1479
1554
|
ink: "bg-foreground text-background"
|
|
1480
1555
|
};
|
|
1556
|
+
function renderTitle(title, emphasis) {
|
|
1557
|
+
if (!emphasis || !title.includes(emphasis)) return title;
|
|
1558
|
+
const parts = title.split(emphasis);
|
|
1559
|
+
return parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1560
|
+
part,
|
|
1561
|
+
i < parts.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary", children: emphasis })
|
|
1562
|
+
] }, i));
|
|
1563
|
+
}
|
|
1481
1564
|
function Hero({
|
|
1482
1565
|
eyebrow,
|
|
1483
1566
|
title,
|
|
1567
|
+
emphasis,
|
|
1484
1568
|
subtitle,
|
|
1485
1569
|
primaryCta,
|
|
1486
1570
|
secondaryCta,
|
|
1487
1571
|
align = "left",
|
|
1488
1572
|
tone = "default",
|
|
1489
1573
|
imageUrl,
|
|
1574
|
+
backgroundImage,
|
|
1575
|
+
overlay = true,
|
|
1576
|
+
eyebrowClassName,
|
|
1490
1577
|
testId
|
|
1491
1578
|
}) {
|
|
1492
|
-
const
|
|
1493
|
-
const
|
|
1494
|
-
const
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1579
|
+
const onDark = Boolean(backgroundImage) || tone === "ink";
|
|
1580
|
+
const subtle = onDark ? "text-background/70" : "text-muted-foreground";
|
|
1581
|
+
const eyebrowClass = eyebrowClassName ?? (onDark ? "text-background/60" : "text-muted-foreground");
|
|
1582
|
+
const centered = align === "center" || Boolean(backgroundImage);
|
|
1583
|
+
const heading = renderTitle(title, emphasis);
|
|
1584
|
+
const copy = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1585
|
+
"div",
|
|
1586
|
+
{
|
|
1587
|
+
className: react.cn(
|
|
1588
|
+
"max-w-2xl",
|
|
1589
|
+
centered && "mx-auto text-center",
|
|
1590
|
+
backgroundImage && "text-background"
|
|
1591
|
+
),
|
|
1592
|
+
children: [
|
|
1593
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: react.cn("mb-4 text-sm font-semibold uppercase tracking-wider", eyebrowClass), children: eyebrow }),
|
|
1594
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl font-semibold tracking-tight text-balance sm:text-5xl", children: heading }),
|
|
1595
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: react.cn("mt-6 text-lg leading-relaxed", subtle), children: subtitle }),
|
|
1596
|
+
(primaryCta?.label || secondaryCta?.label) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1597
|
+
"div",
|
|
1598
|
+
{
|
|
1599
|
+
className: react.cn("mt-8 flex flex-col gap-3 sm:flex-row", centered && "sm:justify-center"),
|
|
1600
|
+
children: [
|
|
1601
|
+
primaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, size: "lg", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: primaryCta.href || "#", children: [
|
|
1602
|
+
primaryCta.label,
|
|
1603
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
1604
|
+
] }) }),
|
|
1605
|
+
secondaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, size: "lg", variant: "outline", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label }) })
|
|
1606
|
+
]
|
|
1607
|
+
}
|
|
1608
|
+
)
|
|
1609
|
+
]
|
|
1610
|
+
}
|
|
1611
|
+
);
|
|
1612
|
+
if (backgroundImage) {
|
|
1613
|
+
const bgStyle = {
|
|
1614
|
+
backgroundImage: `url("${backgroundImage}")`,
|
|
1615
|
+
backgroundSize: "cover",
|
|
1616
|
+
backgroundPosition: "center"
|
|
1617
|
+
};
|
|
1618
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1619
|
+
"section",
|
|
1501
1620
|
{
|
|
1502
|
-
className:
|
|
1621
|
+
className: "relative isolate overflow-hidden",
|
|
1622
|
+
style: bgStyle,
|
|
1623
|
+
"data-testid": testId,
|
|
1624
|
+
"data-bg": testId && `${testId}-bg`,
|
|
1503
1625
|
children: [
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
1507
|
-
] }) }),
|
|
1508
|
-
secondaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, size: "lg", variant: "outline", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label }) })
|
|
1626
|
+
overlay && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
|
|
1627
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative mx-auto max-w-7xl px-5 py-28 lg:px-8 lg:py-36", children: copy })
|
|
1509
1628
|
]
|
|
1510
1629
|
}
|
|
1511
|
-
)
|
|
1512
|
-
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1513
1632
|
return /* @__PURE__ */ jsxRuntime.jsx("section", { className: react.cn(TONE_SURFACE[tone] ?? TONE_SURFACE.default), "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl px-5 py-20 lg:px-8 lg:py-28", children: centered ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-12", children: [
|
|
1514
1633
|
copy,
|
|
1515
1634
|
imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1594,23 +1713,49 @@ var TONE_SURFACE2 = {
|
|
|
1594
1713
|
muted: "bg-muted text-foreground",
|
|
1595
1714
|
ink: "bg-foreground text-background"
|
|
1596
1715
|
};
|
|
1597
|
-
function CTABanner({
|
|
1598
|
-
|
|
1716
|
+
function CTABanner({
|
|
1717
|
+
title,
|
|
1718
|
+
subtitle,
|
|
1719
|
+
cta,
|
|
1720
|
+
tone = "default",
|
|
1721
|
+
backgroundImage,
|
|
1722
|
+
overlay = true,
|
|
1723
|
+
testId
|
|
1724
|
+
}) {
|
|
1725
|
+
const onImage = Boolean(backgroundImage);
|
|
1726
|
+
const subtle = onImage ? "text-white/80" : tone === "default" ? "text-primary-foreground/80" : tone === "ink" ? "text-background/70" : "text-muted-foreground";
|
|
1727
|
+
const bgStyle = backgroundImage ? {
|
|
1728
|
+
backgroundImage: `url("${backgroundImage}")`,
|
|
1729
|
+
backgroundSize: "cover",
|
|
1730
|
+
backgroundPosition: "center"
|
|
1731
|
+
} : void 0;
|
|
1599
1732
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1600
1733
|
"div",
|
|
1601
1734
|
{
|
|
1602
1735
|
className: react.cn(
|
|
1603
|
-
"rounded-2xl px-8 py-14 text-center",
|
|
1604
|
-
TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
|
|
1736
|
+
"relative isolate overflow-hidden rounded-2xl px-8 py-14 text-center",
|
|
1737
|
+
onImage ? "text-white" : TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
|
|
1605
1738
|
),
|
|
1739
|
+
style: bgStyle,
|
|
1606
1740
|
"data-testid": testId,
|
|
1607
1741
|
children: [
|
|
1608
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1613
|
-
|
|
1742
|
+
onImage && overlay && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
|
|
1743
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
1744
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
|
|
1745
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: react.cn("mx-auto mt-4 max-w-2xl text-lg", subtle), children: subtitle }),
|
|
1746
|
+
cta?.label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1747
|
+
react.Button,
|
|
1748
|
+
{
|
|
1749
|
+
asChild: true,
|
|
1750
|
+
size: "lg",
|
|
1751
|
+
variant: tone === "default" && !onImage ? "secondary" : "default",
|
|
1752
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: cta.href || "#", children: [
|
|
1753
|
+
cta.label,
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
1755
|
+
] })
|
|
1756
|
+
}
|
|
1757
|
+
) })
|
|
1758
|
+
] })
|
|
1614
1759
|
]
|
|
1615
1760
|
}
|
|
1616
1761
|
);
|
|
@@ -1775,6 +1920,449 @@ function Container({ width = "default", padding = "md", children, testId }) {
|
|
|
1775
1920
|
}
|
|
1776
1921
|
);
|
|
1777
1922
|
}
|
|
1923
|
+
function PricingTable({ plans, testId }) {
|
|
1924
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1925
|
+
"div",
|
|
1926
|
+
{
|
|
1927
|
+
className: react.cn("grid gap-6", (plans?.length ?? 0) >= 3 ? "md:grid-cols-3" : "sm:grid-cols-2"),
|
|
1928
|
+
"data-testid": testId,
|
|
1929
|
+
children: (plans ?? []).map((plan, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1930
|
+
react.Card,
|
|
1931
|
+
{
|
|
1932
|
+
variant: plan.featured ? "elevated" : "outline",
|
|
1933
|
+
className: react.cn("flex h-full flex-col p-6", plan.featured && "ring-2 ring-primary"),
|
|
1934
|
+
"data-testid": testId && `${testId}-item-${i}`,
|
|
1935
|
+
children: [
|
|
1936
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold tracking-tight", children: plan.name }),
|
|
1937
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-baseline gap-1", children: [
|
|
1938
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl font-semibold tracking-tight", children: plan.price }),
|
|
1939
|
+
plan.period && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
1940
|
+
"/",
|
|
1941
|
+
plan.period
|
|
1942
|
+
] })
|
|
1943
|
+
] }),
|
|
1944
|
+
plan.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: plan.description }),
|
|
1945
|
+
plan.features && plan.features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-5 flex-1 space-y-2", children: plan.features.map((f, j) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
|
|
1946
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "mt-0.5 size-4 shrink-0 text-primary" }),
|
|
1947
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: f })
|
|
1948
|
+
] }, j)) }),
|
|
1949
|
+
plan.cta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, className: "mt-6 w-full", variant: plan.featured ? "default" : "outline", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: plan.cta.href || "#", children: plan.cta.label }) })
|
|
1950
|
+
]
|
|
1951
|
+
},
|
|
1952
|
+
i
|
|
1953
|
+
))
|
|
1954
|
+
}
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
function Tabs({ items, testId }) {
|
|
1958
|
+
const [active, setActive] = react$1.useState(0);
|
|
1959
|
+
const list = items ?? [];
|
|
1960
|
+
const current = list[Math.min(active, Math.max(list.length - 1, 0))];
|
|
1961
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
|
|
1962
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 border-b border-border", role: "tablist", children: list.map((t, i) => {
|
|
1963
|
+
const selected = i === active;
|
|
1964
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1965
|
+
"button",
|
|
1966
|
+
{
|
|
1967
|
+
type: "button",
|
|
1968
|
+
role: "tab",
|
|
1969
|
+
"aria-selected": selected,
|
|
1970
|
+
onClick: () => setActive(i),
|
|
1971
|
+
className: react.cn(
|
|
1972
|
+
"-mb-px border-b-2 px-4 py-2 text-sm font-medium transition-colors",
|
|
1973
|
+
selected ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
|
|
1974
|
+
),
|
|
1975
|
+
"data-testid": testId && `${testId}-tab-${i}`,
|
|
1976
|
+
children: t.label
|
|
1977
|
+
},
|
|
1978
|
+
i
|
|
1979
|
+
);
|
|
1980
|
+
}) }),
|
|
1981
|
+
current && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1982
|
+
"div",
|
|
1983
|
+
{
|
|
1984
|
+
role: "tabpanel",
|
|
1985
|
+
className: "pt-5 text-sm leading-relaxed text-muted-foreground",
|
|
1986
|
+
"data-testid": testId && `${testId}-panel`,
|
|
1987
|
+
children: current.content
|
|
1988
|
+
}
|
|
1989
|
+
)
|
|
1990
|
+
] });
|
|
1991
|
+
}
|
|
1992
|
+
function Accordion({ items, allowMultiple = false, testId }) {
|
|
1993
|
+
const [open, setOpen] = react$1.useState([]);
|
|
1994
|
+
const toggle2 = (i) => setOpen(
|
|
1995
|
+
(prev) => prev.includes(i) ? prev.filter((n) => n !== i) : allowMultiple ? [...prev, i] : [i]
|
|
1996
|
+
);
|
|
1997
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border border-y border-border", "data-testid": testId, children: (items ?? []).map((item, i) => {
|
|
1998
|
+
const isOpen = open.includes(i);
|
|
1999
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
|
|
2000
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2001
|
+
"button",
|
|
2002
|
+
{
|
|
2003
|
+
type: "button",
|
|
2004
|
+
"aria-expanded": isOpen,
|
|
2005
|
+
onClick: () => toggle2(i),
|
|
2006
|
+
className: "flex w-full items-center justify-between gap-4 py-4 text-left text-base font-medium",
|
|
2007
|
+
"data-testid": testId && `${testId}-trigger-${i}`,
|
|
2008
|
+
children: [
|
|
2009
|
+
item.title,
|
|
2010
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2011
|
+
lucideReact.ChevronDown,
|
|
2012
|
+
{
|
|
2013
|
+
className: react.cn("size-4 shrink-0 transition-transform", isOpen && "rotate-180")
|
|
2014
|
+
}
|
|
2015
|
+
)
|
|
2016
|
+
]
|
|
2017
|
+
}
|
|
2018
|
+
),
|
|
2019
|
+
isOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2020
|
+
"p",
|
|
2021
|
+
{
|
|
2022
|
+
className: "pb-4 text-sm leading-relaxed text-muted-foreground",
|
|
2023
|
+
"data-testid": testId && `${testId}-content-${i}`,
|
|
2024
|
+
children: item.content
|
|
2025
|
+
}
|
|
2026
|
+
)
|
|
2027
|
+
] }, i);
|
|
2028
|
+
}) });
|
|
2029
|
+
}
|
|
2030
|
+
var COLS2 = {
|
|
2031
|
+
"2": "grid-cols-2",
|
|
2032
|
+
"3": "grid-cols-2 sm:grid-cols-3",
|
|
2033
|
+
"4": "grid-cols-2 sm:grid-cols-4"
|
|
2034
|
+
};
|
|
2035
|
+
function Gallery({
|
|
2036
|
+
images,
|
|
2037
|
+
columns = "3",
|
|
2038
|
+
aspect = "1:1",
|
|
2039
|
+
rounded = "lg",
|
|
2040
|
+
testId
|
|
2041
|
+
}) {
|
|
2042
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: react.cn("grid gap-4", COLS2[columns] ?? COLS2["3"]), "data-testid": testId, children: (images ?? []).map((img, i) => /* @__PURE__ */ jsxRuntime.jsxs("figure", { "data-testid": testId && `${testId}-item-${i}`, children: [
|
|
2043
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2044
|
+
BlockImage,
|
|
2045
|
+
{
|
|
2046
|
+
src: img.src,
|
|
2047
|
+
alt: img.alt,
|
|
2048
|
+
aspect,
|
|
2049
|
+
rounded,
|
|
2050
|
+
testId: testId && `${testId}-item-${i}-image`
|
|
2051
|
+
}
|
|
2052
|
+
),
|
|
2053
|
+
img.caption && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: img.caption })
|
|
2054
|
+
] }, i)) });
|
|
2055
|
+
}
|
|
2056
|
+
var ASPECT2 = {
|
|
2057
|
+
"16:9": "aspect-video",
|
|
2058
|
+
"4:3": "aspect-[4/3]",
|
|
2059
|
+
"1:1": "aspect-square"
|
|
2060
|
+
};
|
|
2061
|
+
function toEmbedUrl(url) {
|
|
2062
|
+
if (!url) return null;
|
|
2063
|
+
const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]{11})/);
|
|
2064
|
+
if (yt) return `https://www.youtube.com/embed/${yt[1]}`;
|
|
2065
|
+
const vimeo = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
|
|
2066
|
+
if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;
|
|
2067
|
+
const loom = url.match(/loom\.com\/(?:share|embed)\/([\w-]+)/);
|
|
2068
|
+
if (loom) return `https://www.loom.com/embed/${loom[1]}`;
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
function VideoEmbed({
|
|
2072
|
+
url,
|
|
2073
|
+
title,
|
|
2074
|
+
aspect = "16:9",
|
|
2075
|
+
rounded = true,
|
|
2076
|
+
testId
|
|
2077
|
+
}) {
|
|
2078
|
+
const embed = toEmbedUrl(url);
|
|
2079
|
+
const frame = react.cn(
|
|
2080
|
+
"w-full overflow-hidden border border-border bg-muted",
|
|
2081
|
+
ASPECT2[aspect] ?? ASPECT2["16:9"],
|
|
2082
|
+
rounded && "rounded-lg"
|
|
2083
|
+
);
|
|
2084
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frame, "data-testid": testId, children: embed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2085
|
+
"iframe",
|
|
2086
|
+
{
|
|
2087
|
+
src: embed,
|
|
2088
|
+
title: title || "Embedded video",
|
|
2089
|
+
className: "size-full",
|
|
2090
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
2091
|
+
allowFullScreen: true,
|
|
2092
|
+
"data-testid": testId && `${testId}-iframe`
|
|
2093
|
+
}
|
|
2094
|
+
) : url ? /* @__PURE__ */ jsxRuntime.jsx("video", { src: url, controls: true, className: "size-full", "data-testid": testId && `${testId}-video` }) : null });
|
|
2095
|
+
}
|
|
2096
|
+
function ContactForm({
|
|
2097
|
+
fields,
|
|
2098
|
+
submitLabel = "Send",
|
|
2099
|
+
action,
|
|
2100
|
+
successMessage = "Thanks \u2014 we\u2019ll be in touch shortly.",
|
|
2101
|
+
testId
|
|
2102
|
+
}) {
|
|
2103
|
+
const [sent, setSent] = react$1.useState(false);
|
|
2104
|
+
const list = fields ?? [];
|
|
2105
|
+
const onSubmit = (e) => {
|
|
2106
|
+
if (!action) {
|
|
2107
|
+
e.preventDefault();
|
|
2108
|
+
setSent(true);
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
if (sent) {
|
|
2112
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2113
|
+
"p",
|
|
2114
|
+
{
|
|
2115
|
+
className: "rounded-lg border border-border bg-muted p-6 text-center text-sm text-foreground",
|
|
2116
|
+
role: "status",
|
|
2117
|
+
"data-testid": testId && `${testId}-success`,
|
|
2118
|
+
children: successMessage
|
|
2119
|
+
}
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2123
|
+
"form",
|
|
2124
|
+
{
|
|
2125
|
+
action,
|
|
2126
|
+
method: action ? "post" : void 0,
|
|
2127
|
+
onSubmit,
|
|
2128
|
+
className: "mx-auto flex max-w-xl flex-col gap-4",
|
|
2129
|
+
"data-testid": testId,
|
|
2130
|
+
children: [
|
|
2131
|
+
list.map((f, i) => {
|
|
2132
|
+
const inputClass = "w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground outline-none focus:border-primary";
|
|
2133
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-1.5 text-left", children: [
|
|
2134
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: f.label }),
|
|
2135
|
+
f.type === "textarea" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2136
|
+
"textarea",
|
|
2137
|
+
{
|
|
2138
|
+
name: f.name,
|
|
2139
|
+
required: f.required,
|
|
2140
|
+
rows: 4,
|
|
2141
|
+
className: inputClass,
|
|
2142
|
+
"data-testid": testId && `${testId}-field-${f.name}`
|
|
2143
|
+
}
|
|
2144
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2145
|
+
"input",
|
|
2146
|
+
{
|
|
2147
|
+
name: f.name,
|
|
2148
|
+
type: f.type || "text",
|
|
2149
|
+
required: f.required,
|
|
2150
|
+
className: inputClass,
|
|
2151
|
+
"data-testid": testId && `${testId}-field-${f.name}`
|
|
2152
|
+
}
|
|
2153
|
+
)
|
|
2154
|
+
] }, i);
|
|
2155
|
+
}),
|
|
2156
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2157
|
+
react.Button,
|
|
2158
|
+
{
|
|
2159
|
+
type: "submit",
|
|
2160
|
+
className: react.cn("mt-1 self-start"),
|
|
2161
|
+
"data-testid": testId && `${testId}-submit`,
|
|
2162
|
+
children: submitLabel
|
|
2163
|
+
}
|
|
2164
|
+
)
|
|
2165
|
+
]
|
|
2166
|
+
}
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
var TONE = {
|
|
2170
|
+
primary: "bg-primary text-primary-foreground",
|
|
2171
|
+
muted: "bg-muted text-foreground",
|
|
2172
|
+
ink: "bg-foreground text-background"
|
|
2173
|
+
};
|
|
2174
|
+
function Banner({ text, cta, tone = "primary", testId }) {
|
|
2175
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2176
|
+
"div",
|
|
2177
|
+
{
|
|
2178
|
+
className: react.cn("w-full px-5 py-2.5 text-center text-sm", TONE[tone] ?? TONE.primary),
|
|
2179
|
+
"data-testid": testId,
|
|
2180
|
+
children: [
|
|
2181
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: text }),
|
|
2182
|
+
cta?.label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2183
|
+
"a",
|
|
2184
|
+
{
|
|
2185
|
+
href: cta.href || "#",
|
|
2186
|
+
className: "ml-2 inline-flex items-center gap-1 font-semibold underline underline-offset-4",
|
|
2187
|
+
"data-testid": testId && `${testId}-cta`,
|
|
2188
|
+
children: [
|
|
2189
|
+
cta.label,
|
|
2190
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-3.5" })
|
|
2191
|
+
]
|
|
2192
|
+
}
|
|
2193
|
+
)
|
|
2194
|
+
]
|
|
2195
|
+
}
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
function MediaText({
|
|
2199
|
+
eyebrow,
|
|
2200
|
+
title,
|
|
2201
|
+
body,
|
|
2202
|
+
imageUrl,
|
|
2203
|
+
mediaSide = "left",
|
|
2204
|
+
cta,
|
|
2205
|
+
testId
|
|
2206
|
+
}) {
|
|
2207
|
+
const media = /* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: imageUrl, aspect: "4:3", rounded: "lg", testId: testId && `${testId}-image` });
|
|
2208
|
+
const copy = /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2209
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
|
|
2210
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance", children: title }),
|
|
2211
|
+
body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-4 text-lg leading-relaxed text-muted-foreground", children: body }),
|
|
2212
|
+
cta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: cta.href || "#", children: [
|
|
2213
|
+
cta.label,
|
|
2214
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
2215
|
+
] }) })
|
|
2216
|
+
] });
|
|
2217
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid items-center gap-10 lg:grid-cols-2", "data-testid": testId, children: mediaSide === "right" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2218
|
+
copy,
|
|
2219
|
+
media
|
|
2220
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2221
|
+
media,
|
|
2222
|
+
copy
|
|
2223
|
+
] }) });
|
|
2224
|
+
}
|
|
2225
|
+
var COLS3 = {
|
|
2226
|
+
"2": "sm:grid-cols-2",
|
|
2227
|
+
"3": "sm:grid-cols-2 lg:grid-cols-3",
|
|
2228
|
+
"4": "sm:grid-cols-2 lg:grid-cols-4"
|
|
2229
|
+
};
|
|
2230
|
+
function TeamGrid({ members, columns = "3", testId }) {
|
|
2231
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: react.cn("grid gap-6", COLS3[columns] ?? COLS3["3"]), "data-testid": testId, children: (members ?? []).map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2232
|
+
react.Card,
|
|
2233
|
+
{
|
|
2234
|
+
variant: "outline",
|
|
2235
|
+
className: "flex h-full flex-col items-center p-6 text-center",
|
|
2236
|
+
"data-testid": testId && `${testId}-item-${i}`,
|
|
2237
|
+
children: [
|
|
2238
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Avatar, { className: "size-20", children: [
|
|
2239
|
+
m.avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(react.AvatarImage, { src: m.avatarUrl, alt: m.name }),
|
|
2240
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.AvatarFallback, { children: initials(m.name || "?") })
|
|
2241
|
+
] }),
|
|
2242
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: m.name }),
|
|
2243
|
+
m.role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-primary", children: m.role }),
|
|
2244
|
+
m.bio && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: m.bio })
|
|
2245
|
+
]
|
|
2246
|
+
},
|
|
2247
|
+
i
|
|
2248
|
+
)) });
|
|
2249
|
+
}
|
|
2250
|
+
function Steps({ steps, variant = "numbered", testId }) {
|
|
2251
|
+
const list = steps ?? [];
|
|
2252
|
+
if (variant === "timeline") {
|
|
2253
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "relative ml-3 border-l border-border", "data-testid": testId, children: list.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "mb-8 ml-6 last:mb-0", "data-testid": testId && `${testId}-item-${i}`, children: [
|
|
2254
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -left-3 flex size-6 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground", children: i + 1 }),
|
|
2255
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold tracking-tight", children: s.title }),
|
|
2256
|
+
s.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: s.description })
|
|
2257
|
+
] }, i)) });
|
|
2258
|
+
}
|
|
2259
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2260
|
+
"div",
|
|
2261
|
+
{
|
|
2262
|
+
className: react.cn("grid gap-6", list.length >= 4 ? "md:grid-cols-4" : "sm:grid-cols-3"),
|
|
2263
|
+
"data-testid": testId,
|
|
2264
|
+
children: list.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
|
|
2265
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-9 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary", children: i + 1 }),
|
|
2266
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: s.title }),
|
|
2267
|
+
s.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: s.description })
|
|
2268
|
+
] }, i))
|
|
2269
|
+
}
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
var COLS4 = {
|
|
2273
|
+
"2": "sm:grid-cols-2",
|
|
2274
|
+
"3": "sm:grid-cols-2 lg:grid-cols-3"
|
|
2275
|
+
};
|
|
2276
|
+
function TestimonialGrid({ items, columns = "3", testId }) {
|
|
2277
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: react.cn("grid gap-6", COLS4[columns] ?? COLS4["3"]), "data-testid": testId, children: (items ?? []).map((t, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2278
|
+
react.Card,
|
|
2279
|
+
{
|
|
2280
|
+
variant: "outline",
|
|
2281
|
+
className: "flex h-full flex-col p-6",
|
|
2282
|
+
"data-testid": testId && `${testId}-item-${i}`,
|
|
2283
|
+
children: [
|
|
2284
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Quote, { className: "size-6 text-primary/40", "aria-hidden": true }),
|
|
2285
|
+
/* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: "mt-3 flex-1 text-base leading-relaxed", children: t.quote }),
|
|
2286
|
+
(t.author || t.avatarUrl) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-center gap-3", children: [
|
|
2287
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Avatar, { className: "size-9", children: [
|
|
2288
|
+
t.avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(react.AvatarImage, { src: t.avatarUrl, alt: t.author ?? "" }),
|
|
2289
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.AvatarFallback, { children: initials(t.author || "?") })
|
|
2290
|
+
] }),
|
|
2291
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2292
|
+
t.author && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: t.author }),
|
|
2293
|
+
t.role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t.role })
|
|
2294
|
+
] })
|
|
2295
|
+
] })
|
|
2296
|
+
]
|
|
2297
|
+
},
|
|
2298
|
+
i
|
|
2299
|
+
)) });
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// src/studio/publish.ts
|
|
2303
|
+
function blobToDataUrl(blob) {
|
|
2304
|
+
return new Promise((resolve, reject) => {
|
|
2305
|
+
const reader = new FileReader();
|
|
2306
|
+
reader.onload = () => resolve(reader.result);
|
|
2307
|
+
reader.onerror = () => reject(reader.error);
|
|
2308
|
+
reader.readAsDataURL(blob);
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
function createArbiMaterializer(arbi, opts = {}) {
|
|
2312
|
+
return async (docId) => {
|
|
2313
|
+
try {
|
|
2314
|
+
const res = await arbi.documents.download(docId);
|
|
2315
|
+
const blob = await res.blob();
|
|
2316
|
+
if (opts.publishAsset) return await opts.publishAsset(docId, blob);
|
|
2317
|
+
return await blobToDataUrl(blob);
|
|
2318
|
+
} catch {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
function collectAssetRefs(node, acc) {
|
|
2324
|
+
if (typeof node === "string") {
|
|
2325
|
+
if (isAssetRef(node)) acc.add(node);
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
if (Array.isArray(node)) {
|
|
2329
|
+
for (const child of node) collectAssetRefs(child, acc);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
if (node && typeof node === "object") {
|
|
2333
|
+
for (const value of Object.values(node)) collectAssetRefs(value, acc);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
function replaceAssetRefs(node, replacements) {
|
|
2337
|
+
if (typeof node === "string") {
|
|
2338
|
+
return replacements.get(node) ?? node;
|
|
2339
|
+
}
|
|
2340
|
+
if (Array.isArray(node)) {
|
|
2341
|
+
return node.map((child) => replaceAssetRefs(child, replacements));
|
|
2342
|
+
}
|
|
2343
|
+
if (node && typeof node === "object") {
|
|
2344
|
+
const out = {};
|
|
2345
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2346
|
+
out[key] = replaceAssetRefs(value, replacements);
|
|
2347
|
+
}
|
|
2348
|
+
return out;
|
|
2349
|
+
}
|
|
2350
|
+
return node;
|
|
2351
|
+
}
|
|
2352
|
+
async function materializePageData(data, materialize) {
|
|
2353
|
+
const refs = /* @__PURE__ */ new Set();
|
|
2354
|
+
collectAssetRefs(data, refs);
|
|
2355
|
+
if (refs.size === 0) return data;
|
|
2356
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
2357
|
+
await Promise.all(
|
|
2358
|
+
[...refs].map(async (ref) => {
|
|
2359
|
+
const src = await materialize(assetRefId(ref));
|
|
2360
|
+
if (src) replacements.set(ref, src);
|
|
2361
|
+
})
|
|
2362
|
+
);
|
|
2363
|
+
if (replacements.size === 0) return data;
|
|
2364
|
+
return replaceAssetRefs(data, replacements);
|
|
2365
|
+
}
|
|
1778
2366
|
var EMPTY_DATA = { content: [], root: {} };
|
|
1779
2367
|
function StudioEditor({
|
|
1780
2368
|
config,
|
|
@@ -1787,9 +2375,14 @@ function StudioEditor({
|
|
|
1787
2375
|
backLabel = "Back",
|
|
1788
2376
|
live,
|
|
1789
2377
|
initialSlug,
|
|
1790
|
-
testIdPrefix = "studio"
|
|
2378
|
+
testIdPrefix = "studio",
|
|
2379
|
+
publishAsset
|
|
1791
2380
|
}) {
|
|
1792
2381
|
const arbi = react.useArbi();
|
|
2382
|
+
const materialize = react$1.useMemo(
|
|
2383
|
+
() => createArbiMaterializer(arbi, { publishAsset }),
|
|
2384
|
+
[arbi, publishAsset]
|
|
2385
|
+
);
|
|
1793
2386
|
const [slug, setSlug] = react$1.useState(initialSlug ?? pages[0]?.slug ?? "home");
|
|
1794
2387
|
const [data, setData] = react$1.useState(EMPTY_DATA);
|
|
1795
2388
|
const [rev, setRev] = react$1.useState(0);
|
|
@@ -1817,11 +2410,16 @@ function StudioEditor({
|
|
|
1817
2410
|
const persist4 = react$1.useCallback(
|
|
1818
2411
|
async (toSave) => {
|
|
1819
2412
|
setSaving(true);
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2413
|
+
try {
|
|
2414
|
+
const where = await persistence.savePage(arbi, live, slug, toSave);
|
|
2415
|
+
const published = await materializePageData(toSave, materialize);
|
|
2416
|
+
await persistence.savePublished(arbi, live, slug, published);
|
|
2417
|
+
setVia(where);
|
|
2418
|
+
} finally {
|
|
2419
|
+
setSaving(false);
|
|
2420
|
+
}
|
|
1823
2421
|
},
|
|
1824
|
-
[arbi, live, slug, persistence]
|
|
2422
|
+
[arbi, live, slug, persistence, materialize]
|
|
1825
2423
|
);
|
|
1826
2424
|
react$1.useEffect(() => {
|
|
1827
2425
|
let cancelled = false;
|
|
@@ -1851,7 +2449,7 @@ function StudioEditor({
|
|
|
1851
2449
|
reactRouterDom.Link,
|
|
1852
2450
|
{
|
|
1853
2451
|
to: backHref,
|
|
1854
|
-
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:text-
|
|
2452
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:text-primary",
|
|
1855
2453
|
"data-testid": `${testIdPrefix}-back`,
|
|
1856
2454
|
children: [
|
|
1857
2455
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "size-4" }),
|
|
@@ -1862,7 +2460,7 @@ function StudioEditor({
|
|
|
1862
2460
|
),
|
|
1863
2461
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-4 w-px bg-border" })
|
|
1864
2462
|
] }),
|
|
1865
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-display text-sm font-semibold text-
|
|
2463
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-display text-sm font-semibold text-foreground", children: brandLabel }),
|
|
1866
2464
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1867
2465
|
"select",
|
|
1868
2466
|
{
|
|
@@ -1890,7 +2488,7 @@ function StudioEditor({
|
|
|
1890
2488
|
{
|
|
1891
2489
|
type: "button",
|
|
1892
2490
|
onClick: () => setThemeOpen((o) => !o),
|
|
1893
|
-
className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:border-
|
|
2491
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:border-primary hover:text-primary",
|
|
1894
2492
|
"data-testid": `${testIdPrefix}-theme-toggle`,
|
|
1895
2493
|
children: [
|
|
1896
2494
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { className: "size-4" }),
|
|
@@ -1904,7 +2502,7 @@ function StudioEditor({
|
|
|
1904
2502
|
type: "button",
|
|
1905
2503
|
onClick: () => void persist4(latest.current),
|
|
1906
2504
|
disabled: saving || loading,
|
|
1907
|
-
className: "inline-flex items-center gap-1.5 rounded-md bg-
|
|
2505
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-60",
|
|
1908
2506
|
"data-testid": `${testIdPrefix}-publish`,
|
|
1909
2507
|
children: [
|
|
1910
2508
|
saving ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rocket, { className: "size-4" }),
|
|
@@ -1927,7 +2525,7 @@ function StudioEditor({
|
|
|
1927
2525
|
"\u201D\u2026"
|
|
1928
2526
|
]
|
|
1929
2527
|
}
|
|
1930
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2528
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(AssetResolverProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1931
2529
|
puck.Puck,
|
|
1932
2530
|
{
|
|
1933
2531
|
config,
|
|
@@ -1938,7 +2536,7 @@ function StudioEditor({
|
|
|
1938
2536
|
onPublish: (d) => void persist4(d)
|
|
1939
2537
|
},
|
|
1940
2538
|
`${slug}-${rev}`
|
|
1941
|
-
) }),
|
|
2539
|
+
) }) }),
|
|
1942
2540
|
themeOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1943
2541
|
"aside",
|
|
1944
2542
|
{
|
|
@@ -1976,11 +2574,13 @@ function PageRenderer({
|
|
|
1976
2574
|
let cancelled = false;
|
|
1977
2575
|
setLoading(true);
|
|
1978
2576
|
void (async () => {
|
|
1979
|
-
const [{ data:
|
|
1980
|
-
persistence.
|
|
2577
|
+
const [{ data: published }, { data: theme }] = await Promise.all([
|
|
2578
|
+
persistence.loadPublished(arbi, live, slug),
|
|
1981
2579
|
persistence.loadTheme(arbi, live)
|
|
1982
2580
|
]);
|
|
1983
2581
|
if (cancelled) return;
|
|
2582
|
+
const page = published ?? (await persistence.loadPage(arbi, live, slug)).data;
|
|
2583
|
+
if (cancelled) return;
|
|
1984
2584
|
applyThemeVars(
|
|
1985
2585
|
theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
|
|
1986
2586
|
);
|
|
@@ -2010,7 +2610,7 @@ function PageRenderer({
|
|
|
2010
2610
|
className: "mx-auto max-w-2xl px-5 py-24 text-center",
|
|
2011
2611
|
"data-testid": `${testIdPrefix}-render-empty`,
|
|
2012
2612
|
children: [
|
|
2013
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-display text-2xl font-semibold text-
|
|
2613
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-display text-2xl font-semibold text-foreground", children: title }),
|
|
2014
2614
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: body })
|
|
2015
2615
|
]
|
|
2016
2616
|
}
|
|
@@ -2024,9 +2624,11 @@ function createStudioPersistence(cfg) {
|
|
|
2024
2624
|
const WORKSPACE_ID = cfg.workspaceId;
|
|
2025
2625
|
const FOLDER = cfg.folder ?? "site";
|
|
2026
2626
|
const pageKey = (slug) => `${cfg.storagePrefix}${slug}`;
|
|
2627
|
+
const publishedKey = (slug) => `${cfg.storagePrefix}published-${slug}`;
|
|
2027
2628
|
const THEME_KEY = cfg.themeStorageKey ?? `${cfg.storagePrefix}theme`;
|
|
2028
2629
|
const THEME_FILE = cfg.themeFileName ?? "theme.json";
|
|
2029
2630
|
const pageFile = cfg.pageFileName ?? ((slug) => `${slug}.json`);
|
|
2631
|
+
const publishedFile = cfg.publishedFileName ?? ((slug) => `${slug}.published.json`);
|
|
2030
2632
|
function canUseArbi(arbi, live) {
|
|
2031
2633
|
return live && !!arbi;
|
|
2032
2634
|
}
|
|
@@ -2049,39 +2651,47 @@ function createStudioPersistence(cfg) {
|
|
|
2049
2651
|
return JSON.parse(raw);
|
|
2050
2652
|
}
|
|
2051
2653
|
}
|
|
2052
|
-
async function
|
|
2654
|
+
async function writePage(arbi, live, kind, localKey, fileName, data) {
|
|
2053
2655
|
try {
|
|
2054
|
-
localStorage.setItem(
|
|
2656
|
+
localStorage.setItem(localKey, JSON.stringify(data));
|
|
2055
2657
|
} catch {
|
|
2056
2658
|
}
|
|
2057
2659
|
if (canUseArbi(arbi, live)) {
|
|
2058
2660
|
try {
|
|
2059
|
-
await uploadJson(arbi,
|
|
2060
|
-
console.info(`[studio] saved
|
|
2661
|
+
await uploadJson(arbi, fileName, data);
|
|
2662
|
+
console.info(`[studio] saved ${kind} "${fileName}" \u2192 ARBI (${WORKSPACE_ID})`);
|
|
2061
2663
|
return "arbi";
|
|
2062
2664
|
} catch (e) {
|
|
2063
|
-
console.warn(`[studio] ARBI save failed for "${
|
|
2665
|
+
console.warn(`[studio] ARBI save failed for "${fileName}", kept localStorage`, e);
|
|
2064
2666
|
}
|
|
2065
2667
|
}
|
|
2066
|
-
console.info(`[studio] saved
|
|
2668
|
+
console.info(`[studio] saved ${kind} "${fileName}" \u2192 localStorage`);
|
|
2067
2669
|
return "local";
|
|
2068
2670
|
}
|
|
2069
|
-
async function
|
|
2671
|
+
async function readPage(arbi, live, localKey, fileName) {
|
|
2070
2672
|
if (canUseArbi(arbi, live)) {
|
|
2071
2673
|
try {
|
|
2072
|
-
const data = await downloadJson(arbi,
|
|
2073
|
-
if (data) {
|
|
2074
|
-
console.info(`[studio] loaded page "${slug}" \u2190 ARBI`);
|
|
2075
|
-
return { data, via: "arbi" };
|
|
2076
|
-
}
|
|
2674
|
+
const data = await downloadJson(arbi, fileName);
|
|
2675
|
+
if (data) return { data, via: "arbi" };
|
|
2077
2676
|
} catch (e) {
|
|
2078
|
-
console.warn(`[studio] ARBI load failed for "${
|
|
2677
|
+
console.warn(`[studio] ARBI load failed for "${fileName}", trying localStorage`, e);
|
|
2079
2678
|
}
|
|
2080
2679
|
}
|
|
2081
|
-
const raw = safeLocalGet(
|
|
2082
|
-
console.info(`[studio] loaded page "${slug}" \u2190 localStorage`);
|
|
2680
|
+
const raw = safeLocalGet(localKey);
|
|
2083
2681
|
return { data: raw ? JSON.parse(raw) : null, via: "local" };
|
|
2084
2682
|
}
|
|
2683
|
+
function savePage(arbi, live, slug, data) {
|
|
2684
|
+
return writePage(arbi, live, "page", pageKey(slug), pageFile(slug), data);
|
|
2685
|
+
}
|
|
2686
|
+
function loadPage(arbi, live, slug) {
|
|
2687
|
+
return readPage(arbi, live, pageKey(slug), pageFile(slug));
|
|
2688
|
+
}
|
|
2689
|
+
function savePublished(arbi, live, slug, data) {
|
|
2690
|
+
return writePage(arbi, live, "published page", publishedKey(slug), publishedFile(slug), data);
|
|
2691
|
+
}
|
|
2692
|
+
function loadPublished(arbi, live, slug) {
|
|
2693
|
+
return readPage(arbi, live, publishedKey(slug), publishedFile(slug));
|
|
2694
|
+
}
|
|
2085
2695
|
async function saveTheme(arbi, live, theme) {
|
|
2086
2696
|
try {
|
|
2087
2697
|
localStorage.setItem(THEME_KEY, JSON.stringify(theme));
|
|
@@ -2118,7 +2728,7 @@ function createStudioPersistence(cfg) {
|
|
|
2118
2728
|
return null;
|
|
2119
2729
|
}
|
|
2120
2730
|
}
|
|
2121
|
-
return { savePage, loadPage, saveTheme, loadTheme };
|
|
2731
|
+
return { savePage, loadPage, savePublished, loadPublished, saveTheme, loadTheme };
|
|
2122
2732
|
}
|
|
2123
2733
|
function AiTextField({
|
|
2124
2734
|
value,
|
|
@@ -2233,14 +2843,286 @@ function createAiFields(cfg) {
|
|
|
2233
2843
|
});
|
|
2234
2844
|
return { aiText, aiTextarea };
|
|
2235
2845
|
}
|
|
2236
|
-
function
|
|
2237
|
-
|
|
2238
|
-
|
|
2846
|
+
function latestImageDocId(artifacts) {
|
|
2847
|
+
for (let i = artifacts.length - 1; i >= 0; i--) {
|
|
2848
|
+
if (artifacts[i].mode === "image") return artifacts[i].doc_ext_id;
|
|
2849
|
+
}
|
|
2850
|
+
return null;
|
|
2239
2851
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2852
|
+
function useImageGen() {
|
|
2853
|
+
const task = useFirmAiTask();
|
|
2854
|
+
const { run } = task;
|
|
2855
|
+
const generate = react$1.useCallback(
|
|
2856
|
+
async (prompt) => {
|
|
2857
|
+
const trimmed = prompt.trim();
|
|
2858
|
+
if (!trimmed) return null;
|
|
2859
|
+
const result = await run(`/image ${trimmed}`, []);
|
|
2860
|
+
return latestImageDocId(result?.artifacts ?? []);
|
|
2861
|
+
},
|
|
2862
|
+
[run]
|
|
2863
|
+
);
|
|
2864
|
+
const docExtId = react$1.useMemo(() => latestImageDocId(task.artifacts), [task.artifacts]);
|
|
2865
|
+
return {
|
|
2866
|
+
docExtId,
|
|
2867
|
+
status: task.status,
|
|
2868
|
+
error: task.error,
|
|
2869
|
+
isGenerating: task.isRunning,
|
|
2870
|
+
generate,
|
|
2871
|
+
reset: task.reset
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
var IMAGE_EXT = /\.(png|jpe?g|webp|gif|avif|svg)$/i;
|
|
2875
|
+
function ImageFieldControl({
|
|
2876
|
+
value,
|
|
2877
|
+
onChange,
|
|
2878
|
+
name,
|
|
2879
|
+
workspaceId,
|
|
2880
|
+
folder,
|
|
2881
|
+
testIdPrefix
|
|
2882
|
+
}) {
|
|
2883
|
+
const [mode, setMode] = react$1.useState("asset");
|
|
2884
|
+
const [url, setUrl] = react$1.useState("");
|
|
2885
|
+
const [prompt, setPrompt] = react$1.useState("");
|
|
2886
|
+
const [busy, setBusy] = react$1.useState(false);
|
|
2887
|
+
const arbi = react.useArbi();
|
|
2888
|
+
const gen = useImageGen();
|
|
2889
|
+
const tp = (...q) => tid(`${testIdPrefix}-image`, name, ...q);
|
|
2890
|
+
const { data: docs } = react.useDocuments(workspaceId);
|
|
2891
|
+
const imageDocs = react$1.useMemo(
|
|
2892
|
+
() => (docs ?? []).filter(
|
|
2893
|
+
(d) => d.file_name && IMAGE_EXT.test(d.file_name)
|
|
2894
|
+
),
|
|
2895
|
+
[docs]
|
|
2896
|
+
);
|
|
2897
|
+
const imageIds = react$1.useMemo(
|
|
2898
|
+
() => imageDocs.map((d) => d.external_id),
|
|
2899
|
+
[imageDocs]
|
|
2900
|
+
);
|
|
2901
|
+
const { data: thumbs } = react.useThumbnails(imageIds);
|
|
2902
|
+
const onUpload = async (e) => {
|
|
2903
|
+
const file = e.target.files?.[0];
|
|
2904
|
+
if (!file) return;
|
|
2905
|
+
setBusy(true);
|
|
2906
|
+
try {
|
|
2907
|
+
await arbi.selectWorkspace(workspaceId);
|
|
2908
|
+
const res = await arbi.documents.uploadFile(file, file.name, { folder });
|
|
2909
|
+
const id = res.doc_ext_ids?.[0];
|
|
2910
|
+
if (id) onChange(asAssetRef(id));
|
|
2911
|
+
} finally {
|
|
2912
|
+
setBusy(false);
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
const onGenerate = async () => {
|
|
2916
|
+
if (!prompt.trim() || gen.isGenerating) return;
|
|
2917
|
+
const id = await gen.generate(prompt);
|
|
2918
|
+
if (id) onChange(asAssetRef(id));
|
|
2919
|
+
};
|
|
2920
|
+
const MODES = [
|
|
2921
|
+
{ key: "asset", label: "Assets", icon: lucideReact.ImagePlus },
|
|
2922
|
+
{ key: "upload", label: "Upload", icon: lucideReact.Upload },
|
|
2923
|
+
{ key: "url", label: "URL", icon: lucideReact.Link2 },
|
|
2924
|
+
{ key: "generate", label: "Generate", icon: lucideReact.Sparkles }
|
|
2925
|
+
];
|
|
2926
|
+
const inputClass = "w-full rounded-md border border-border bg-card px-2.5 py-1.5 text-sm text-foreground outline-none focus:border-primary";
|
|
2927
|
+
const tabClass = (on) => `inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium ${on ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`;
|
|
2928
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", "data-testid": tp(), children: [
|
|
2929
|
+
/* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: value, aspect: "16:9", rounded: "md", testId: tp("preview") }),
|
|
2930
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: MODES.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2931
|
+
"button",
|
|
2932
|
+
{
|
|
2933
|
+
type: "button",
|
|
2934
|
+
className: tabClass(mode === m.key),
|
|
2935
|
+
onClick: () => setMode(m.key),
|
|
2936
|
+
"data-testid": tp("mode", m.key),
|
|
2937
|
+
children: [
|
|
2938
|
+
/* @__PURE__ */ jsxRuntime.jsx(m.icon, { className: "size-3" }),
|
|
2939
|
+
" ",
|
|
2940
|
+
m.label
|
|
2941
|
+
]
|
|
2942
|
+
},
|
|
2943
|
+
m.key
|
|
2944
|
+
)) }),
|
|
2945
|
+
mode === "asset" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid max-h-48 grid-cols-3 gap-1.5 overflow-auto", "data-testid": tp("assets"), children: [
|
|
2946
|
+
imageDocs.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "col-span-3 py-3 text-center text-xs text-muted-foreground", children: "No image assets yet \u2014 upload or generate one." }),
|
|
2947
|
+
imageDocs.map((d) => {
|
|
2948
|
+
const thumb = thumbs?.[d.external_id];
|
|
2949
|
+
const selected = value === asAssetRef(d.external_id);
|
|
2950
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2951
|
+
"button",
|
|
2952
|
+
{
|
|
2953
|
+
type: "button",
|
|
2954
|
+
onClick: () => onChange(asAssetRef(d.external_id)),
|
|
2955
|
+
className: `overflow-hidden rounded border ${selected ? "border-primary ring-1 ring-primary" : "border-border"}`,
|
|
2956
|
+
title: d.file_name ?? "",
|
|
2957
|
+
"data-testid": tp("asset", d.external_id),
|
|
2958
|
+
children: thumb ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2959
|
+
"img",
|
|
2960
|
+
{
|
|
2961
|
+
src: thumb,
|
|
2962
|
+
alt: d.file_name ?? "",
|
|
2963
|
+
className: "aspect-square w-full object-cover"
|
|
2964
|
+
}
|
|
2965
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex aspect-square w-full items-center justify-center bg-muted text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImagePlus, { className: "size-4" }) })
|
|
2966
|
+
},
|
|
2967
|
+
d.external_id
|
|
2968
|
+
);
|
|
2969
|
+
})
|
|
2970
|
+
] }),
|
|
2971
|
+
mode === "upload" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "inline-flex cursor-pointer items-center gap-2 rounded-md border border-border px-2.5 py-1.5 text-sm text-foreground", children: [
|
|
2972
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "size-4" }),
|
|
2973
|
+
busy ? "Uploading\u2026" : "Choose a file",
|
|
2974
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2975
|
+
"input",
|
|
2976
|
+
{
|
|
2977
|
+
type: "file",
|
|
2978
|
+
accept: "image/*",
|
|
2979
|
+
className: "hidden",
|
|
2980
|
+
disabled: busy,
|
|
2981
|
+
onChange: (e) => void onUpload(e),
|
|
2982
|
+
"data-testid": tp("upload")
|
|
2983
|
+
}
|
|
2984
|
+
)
|
|
2985
|
+
] }),
|
|
2986
|
+
mode === "url" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
|
|
2987
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2988
|
+
"input",
|
|
2989
|
+
{
|
|
2990
|
+
className: inputClass,
|
|
2991
|
+
placeholder: "https://\u2026",
|
|
2992
|
+
value: url,
|
|
2993
|
+
onChange: (e) => setUrl(e.target.value),
|
|
2994
|
+
"data-testid": tp("url")
|
|
2995
|
+
}
|
|
2996
|
+
),
|
|
2997
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2998
|
+
"button",
|
|
2999
|
+
{
|
|
3000
|
+
type: "button",
|
|
3001
|
+
className: "rounded-md bg-primary px-2.5 py-1.5 text-sm font-medium text-primary-foreground disabled:opacity-50",
|
|
3002
|
+
disabled: !url.trim(),
|
|
3003
|
+
onClick: () => onChange(url.trim()),
|
|
3004
|
+
"data-testid": tp("url-apply"),
|
|
3005
|
+
children: "Use"
|
|
3006
|
+
}
|
|
3007
|
+
)
|
|
3008
|
+
] }),
|
|
3009
|
+
mode === "generate" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
3010
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3011
|
+
"textarea",
|
|
3012
|
+
{
|
|
3013
|
+
className: inputClass,
|
|
3014
|
+
rows: 2,
|
|
3015
|
+
placeholder: "Describe the image\u2026",
|
|
3016
|
+
value: prompt,
|
|
3017
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
3018
|
+
"data-testid": tp("prompt")
|
|
3019
|
+
}
|
|
3020
|
+
),
|
|
3021
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3022
|
+
"button",
|
|
3023
|
+
{
|
|
3024
|
+
type: "button",
|
|
3025
|
+
className: "inline-flex items-center gap-1.5 self-start rounded-md bg-primary px-2.5 py-1.5 text-sm font-medium text-primary-foreground disabled:opacity-50",
|
|
3026
|
+
disabled: !prompt.trim() || gen.isGenerating,
|
|
3027
|
+
onClick: () => void onGenerate(),
|
|
3028
|
+
"data-testid": tp("generate"),
|
|
3029
|
+
children: [
|
|
3030
|
+
gen.isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-3.5" }),
|
|
3031
|
+
gen.isGenerating ? "Generating\u2026" : "Generate"
|
|
3032
|
+
]
|
|
3033
|
+
}
|
|
3034
|
+
),
|
|
3035
|
+
gen.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive", role: "alert", children: gen.error })
|
|
3036
|
+
] }),
|
|
3037
|
+
value && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3038
|
+
"button",
|
|
3039
|
+
{
|
|
3040
|
+
type: "button",
|
|
3041
|
+
className: "self-start text-xs text-muted-foreground hover:text-foreground",
|
|
3042
|
+
onClick: () => onChange(""),
|
|
3043
|
+
"data-testid": tp("clear"),
|
|
3044
|
+
children: "Clear"
|
|
3045
|
+
}
|
|
3046
|
+
)
|
|
3047
|
+
] });
|
|
3048
|
+
}
|
|
3049
|
+
function createImageField(cfg) {
|
|
3050
|
+
const { workspaceId } = cfg;
|
|
3051
|
+
const folder = cfg.folder ?? "assets";
|
|
3052
|
+
const testIdPrefix = cfg.testIdPrefix ?? "studio";
|
|
3053
|
+
const imageField = (label) => ({
|
|
3054
|
+
type: "custom",
|
|
3055
|
+
label,
|
|
3056
|
+
render: ({ value, onChange, name }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3057
|
+
ImageFieldControl,
|
|
3058
|
+
{
|
|
3059
|
+
value: value ?? "",
|
|
3060
|
+
onChange,
|
|
3061
|
+
name,
|
|
3062
|
+
workspaceId,
|
|
3063
|
+
folder,
|
|
3064
|
+
testIdPrefix
|
|
3065
|
+
}
|
|
3066
|
+
)
|
|
3067
|
+
});
|
|
3068
|
+
return { imageField };
|
|
3069
|
+
}
|
|
3070
|
+
var TEXTURE_STYLE = {
|
|
3071
|
+
backgroundImage: "radial-gradient(currentColor 0.5px, transparent 0.5px)",
|
|
3072
|
+
backgroundSize: "4px 4px",
|
|
3073
|
+
opacity: 0.04
|
|
3074
|
+
};
|
|
3075
|
+
var PADDING2 = {
|
|
3076
|
+
none: "",
|
|
3077
|
+
sm: "py-8 lg:py-10",
|
|
3078
|
+
md: "py-16 lg:py-20",
|
|
3079
|
+
lg: "py-24 lg:py-28"
|
|
3080
|
+
};
|
|
3081
|
+
var WIDTH2 = {
|
|
3082
|
+
narrow: "max-w-3xl",
|
|
3083
|
+
default: "max-w-7xl",
|
|
3084
|
+
wide: "max-w-[90rem]",
|
|
3085
|
+
full: "max-w-none"
|
|
3086
|
+
};
|
|
3087
|
+
var INVERTED_VARS = {
|
|
3088
|
+
["--color-background"]: "var(--color-foreground)",
|
|
3089
|
+
["--color-foreground"]: "var(--color-background)",
|
|
3090
|
+
["--color-card"]: "color-mix(in srgb, var(--color-foreground) 92%, #fff)",
|
|
3091
|
+
["--color-muted"]: "color-mix(in srgb, var(--color-foreground) 85%, #fff)",
|
|
3092
|
+
["--color-muted-foreground"]: "color-mix(in srgb, var(--color-background) 60%, transparent)",
|
|
3093
|
+
["--color-border"]: "color-mix(in srgb, var(--color-background) 18%, transparent)",
|
|
3094
|
+
["--color-accent"]: "color-mix(in srgb, var(--color-foreground) 80%, #fff)"
|
|
3095
|
+
};
|
|
3096
|
+
function Section({
|
|
3097
|
+
children,
|
|
3098
|
+
tone = "default",
|
|
3099
|
+
padding = "md",
|
|
3100
|
+
maxWidth = "default",
|
|
3101
|
+
texture,
|
|
3102
|
+
className = ""
|
|
3103
|
+
}) {
|
|
3104
|
+
const inverted = tone === "ink";
|
|
3105
|
+
const toneClass = inverted ? "bg-background text-foreground" : tone === "muted" ? "border-y border-border bg-card/40" : "";
|
|
3106
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: react.cn("relative", toneClass), style: inverted ? INVERTED_VARS : void 0, children: [
|
|
3107
|
+
texture && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0", style: TEXTURE_STYLE, "aria-hidden": true }),
|
|
3108
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3109
|
+
"div",
|
|
3110
|
+
{
|
|
3111
|
+
className: react.cn(
|
|
3112
|
+
"relative mx-auto px-5 lg:px-8",
|
|
3113
|
+
WIDTH2[maxWidth],
|
|
3114
|
+
PADDING2[padding],
|
|
3115
|
+
className
|
|
3116
|
+
),
|
|
3117
|
+
children
|
|
3118
|
+
}
|
|
3119
|
+
)
|
|
3120
|
+
] });
|
|
3121
|
+
}
|
|
3122
|
+
var iconMap = {
|
|
3123
|
+
FileText: lucideReact.FileText,
|
|
3124
|
+
Folder: lucideReact.Folder,
|
|
3125
|
+
Users: lucideReact.Users,
|
|
2244
3126
|
Briefcase: lucideReact.Briefcase,
|
|
2245
3127
|
Calendar: lucideReact.Calendar,
|
|
2246
3128
|
Clock: lucideReact.Clock,
|
|
@@ -2278,10 +3160,12 @@ var ICON_OPTIONS = [
|
|
|
2278
3160
|
{ label: "None", value: "" },
|
|
2279
3161
|
...Object.keys(iconMap).map((name) => ({ label: name, value: name }))
|
|
2280
3162
|
];
|
|
2281
|
-
function createBlockKit(ai) {
|
|
3163
|
+
function createBlockKit(ai, image, eyebrowClassName) {
|
|
2282
3164
|
return {
|
|
3165
|
+
eyebrowClassName,
|
|
2283
3166
|
textField: (label, placeholder) => ai ? ai.aiText(label, placeholder) : { type: "text", label, placeholder },
|
|
2284
3167
|
textareaField: (label, placeholder, kind) => ai ? ai.aiTextarea(label, placeholder, kind) : { type: "textarea", label, placeholder },
|
|
3168
|
+
imageField: (label) => image ? image(label) : { type: "text", label },
|
|
2285
3169
|
boolRadio: (label) => ({
|
|
2286
3170
|
type: "radio",
|
|
2287
3171
|
label,
|
|
@@ -2311,7 +3195,7 @@ var toneField = {
|
|
|
2311
3195
|
]
|
|
2312
3196
|
};
|
|
2313
3197
|
function createWebsiteBlocks(kit) {
|
|
2314
|
-
const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
|
|
3198
|
+
const { textField, textareaField, imageField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
|
|
2315
3199
|
const ctaField = (label) => ({
|
|
2316
3200
|
type: "object",
|
|
2317
3201
|
label,
|
|
@@ -2410,6 +3294,7 @@ function createWebsiteBlocks(kit) {
|
|
|
2410
3294
|
fields: {
|
|
2411
3295
|
eyebrow: textField("Eyebrow"),
|
|
2412
3296
|
title: textField("Title"),
|
|
3297
|
+
emphasis: { type: "text", label: "Emphasised word" },
|
|
2413
3298
|
subtitle: textareaField("Subtitle", void 0, "lead"),
|
|
2414
3299
|
primaryCta: ctaField("Primary CTA"),
|
|
2415
3300
|
secondaryCta: ctaField("Secondary CTA"),
|
|
@@ -2422,29 +3307,51 @@ function createWebsiteBlocks(kit) {
|
|
|
2422
3307
|
]
|
|
2423
3308
|
},
|
|
2424
3309
|
tone: toneField,
|
|
2425
|
-
imageUrl:
|
|
3310
|
+
imageUrl: imageField("Side image"),
|
|
3311
|
+
backgroundImage: imageField("Background image"),
|
|
3312
|
+
overlay: boolRadio("Background overlay")
|
|
2426
3313
|
},
|
|
2427
3314
|
defaultProps: {
|
|
2428
3315
|
eyebrow: "Introducing",
|
|
2429
3316
|
title: "Build a better website, faster.",
|
|
3317
|
+
emphasis: "",
|
|
2430
3318
|
subtitle: "A flexible platform that helps your team ship polished, on-brand pages without waiting on engineering.",
|
|
2431
3319
|
primaryCta: { label: "Get started", href: "#" },
|
|
2432
3320
|
secondaryCta: { label: "Learn more", href: "#" },
|
|
2433
3321
|
align: "left",
|
|
2434
3322
|
tone: "default",
|
|
2435
|
-
imageUrl: ""
|
|
3323
|
+
imageUrl: "",
|
|
3324
|
+
backgroundImage: "",
|
|
3325
|
+
overlay: true
|
|
2436
3326
|
},
|
|
2437
|
-
render: ({
|
|
3327
|
+
render: ({
|
|
3328
|
+
id,
|
|
3329
|
+
eyebrow,
|
|
3330
|
+
title,
|
|
3331
|
+
emphasis,
|
|
3332
|
+
subtitle,
|
|
3333
|
+
primaryCta,
|
|
3334
|
+
secondaryCta,
|
|
3335
|
+
align,
|
|
3336
|
+
tone,
|
|
3337
|
+
imageUrl,
|
|
3338
|
+
backgroundImage,
|
|
3339
|
+
overlay
|
|
3340
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2438
3341
|
Hero,
|
|
2439
3342
|
{
|
|
2440
3343
|
eyebrow,
|
|
2441
3344
|
title,
|
|
3345
|
+
emphasis,
|
|
2442
3346
|
subtitle,
|
|
2443
3347
|
primaryCta,
|
|
2444
3348
|
secondaryCta,
|
|
2445
3349
|
align,
|
|
2446
3350
|
tone,
|
|
2447
3351
|
imageUrl,
|
|
3352
|
+
backgroundImage,
|
|
3353
|
+
overlay,
|
|
3354
|
+
eyebrowClassName: kit.eyebrowClassName,
|
|
2448
3355
|
testId: blockTestId(id)
|
|
2449
3356
|
}
|
|
2450
3357
|
)
|
|
@@ -2561,15 +3468,30 @@ function createWebsiteBlocks(kit) {
|
|
|
2561
3468
|
title: textField("Title"),
|
|
2562
3469
|
subtitle: textareaField("Subtitle"),
|
|
2563
3470
|
cta: ctaField("Button"),
|
|
2564
|
-
tone: toneField
|
|
3471
|
+
tone: toneField,
|
|
3472
|
+
backgroundImage: imageField("Background image"),
|
|
3473
|
+
overlay: boolRadio("Background overlay")
|
|
2565
3474
|
},
|
|
2566
3475
|
defaultProps: {
|
|
2567
3476
|
title: "Ready to get started?",
|
|
2568
3477
|
subtitle: "Join thousands of teams already building with us.",
|
|
2569
3478
|
cta: { label: "Start free trial", href: "#" },
|
|
2570
|
-
tone: "default"
|
|
3479
|
+
tone: "default",
|
|
3480
|
+
backgroundImage: "",
|
|
3481
|
+
overlay: true
|
|
2571
3482
|
},
|
|
2572
|
-
render: ({ id, title, subtitle, cta, tone }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3483
|
+
render: ({ id, title, subtitle, cta, tone, backgroundImage, overlay }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3484
|
+
CTABanner,
|
|
3485
|
+
{
|
|
3486
|
+
title,
|
|
3487
|
+
subtitle,
|
|
3488
|
+
cta,
|
|
3489
|
+
tone,
|
|
3490
|
+
backgroundImage,
|
|
3491
|
+
overlay,
|
|
3492
|
+
testId: blockTestId(id)
|
|
3493
|
+
}
|
|
3494
|
+
)
|
|
2573
3495
|
};
|
|
2574
3496
|
const LogoCloudBlock = {
|
|
2575
3497
|
label: "Logo cloud",
|
|
@@ -2661,7 +3583,7 @@ function createWebsiteBlocks(kit) {
|
|
|
2661
3583
|
const ImageBlock = {
|
|
2662
3584
|
label: "Image",
|
|
2663
3585
|
fields: {
|
|
2664
|
-
src:
|
|
3586
|
+
src: imageField("Image"),
|
|
2665
3587
|
alt: textField("Alt text"),
|
|
2666
3588
|
aspect: {
|
|
2667
3589
|
type: "radio",
|
|
@@ -2682,10 +3604,21 @@ function createWebsiteBlocks(kit) {
|
|
|
2682
3604
|
{ label: "L", value: "lg" },
|
|
2683
3605
|
{ label: "Full", value: "full" }
|
|
2684
3606
|
]
|
|
2685
|
-
}
|
|
3607
|
+
},
|
|
3608
|
+
framed: boolRadio("Framed")
|
|
2686
3609
|
},
|
|
2687
|
-
defaultProps: { src: "", alt: "Image", aspect: "16:9", rounded: "lg" },
|
|
2688
|
-
render: ({ id, src, alt, aspect, rounded }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3610
|
+
defaultProps: { src: "", alt: "Image", aspect: "16:9", rounded: "lg", framed: false },
|
|
3611
|
+
render: ({ id, src, alt, aspect, rounded, framed }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3612
|
+
BlockImage,
|
|
3613
|
+
{
|
|
3614
|
+
src,
|
|
3615
|
+
alt,
|
|
3616
|
+
aspect,
|
|
3617
|
+
rounded,
|
|
3618
|
+
framed,
|
|
3619
|
+
testId: blockTestId(id)
|
|
3620
|
+
}
|
|
3621
|
+
)
|
|
2689
3622
|
};
|
|
2690
3623
|
const AvatarBlockConfig = {
|
|
2691
3624
|
label: "Avatar",
|
|
@@ -2831,8 +3764,440 @@ function createWebsiteBlocks(kit) {
|
|
|
2831
3764
|
List: ListBlockConfig
|
|
2832
3765
|
};
|
|
2833
3766
|
}
|
|
3767
|
+
var lines = (s) => (s ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3768
|
+
function createSiteBlocks(kit) {
|
|
3769
|
+
const { textField, textareaField, imageField, boolRadio, blockTestId } = kit;
|
|
3770
|
+
const ctaField = (label) => ({
|
|
3771
|
+
type: "object",
|
|
3772
|
+
label,
|
|
3773
|
+
objectFields: {
|
|
3774
|
+
label: textField("Label"),
|
|
3775
|
+
href: { type: "text", label: "Link" }
|
|
3776
|
+
}
|
|
3777
|
+
});
|
|
3778
|
+
const columnsField = (values) => ({
|
|
3779
|
+
type: "radio",
|
|
3780
|
+
label: "Columns",
|
|
3781
|
+
options: values.map((v) => ({ label: v, value: v }))
|
|
3782
|
+
});
|
|
3783
|
+
const PricingBlock = {
|
|
3784
|
+
label: "Pricing",
|
|
3785
|
+
fields: {
|
|
3786
|
+
plans: {
|
|
3787
|
+
type: "array",
|
|
3788
|
+
label: "Plans",
|
|
3789
|
+
getItemSummary: (item) => item?.name || "Plan",
|
|
3790
|
+
defaultItemProps: {
|
|
3791
|
+
name: "Plan",
|
|
3792
|
+
price: "\xA30",
|
|
3793
|
+
period: "mo",
|
|
3794
|
+
description: "",
|
|
3795
|
+
features: "Feature one\nFeature two",
|
|
3796
|
+
ctaLabel: "Choose",
|
|
3797
|
+
ctaHref: "#",
|
|
3798
|
+
featured: false
|
|
3799
|
+
},
|
|
3800
|
+
arrayFields: {
|
|
3801
|
+
name: textField("Name"),
|
|
3802
|
+
price: { type: "text", label: "Price" },
|
|
3803
|
+
period: { type: "text", label: "Period" },
|
|
3804
|
+
description: textareaField("Description"),
|
|
3805
|
+
features: { type: "textarea", label: "Features (one per line)" },
|
|
3806
|
+
ctaLabel: { type: "text", label: "Button label" },
|
|
3807
|
+
ctaHref: { type: "text", label: "Button link" },
|
|
3808
|
+
featured: boolRadio("Featured")
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
},
|
|
3812
|
+
defaultProps: {
|
|
3813
|
+
plans: [
|
|
3814
|
+
{
|
|
3815
|
+
name: "Starter",
|
|
3816
|
+
price: "\xA30",
|
|
3817
|
+
period: "mo",
|
|
3818
|
+
description: "For getting started.",
|
|
3819
|
+
features: "Up to 3 projects\nCommunity support",
|
|
3820
|
+
ctaLabel: "Get started",
|
|
3821
|
+
ctaHref: "#",
|
|
3822
|
+
featured: false
|
|
3823
|
+
},
|
|
3824
|
+
{
|
|
3825
|
+
name: "Pro",
|
|
3826
|
+
price: "\xA329",
|
|
3827
|
+
period: "mo",
|
|
3828
|
+
description: "For growing teams.",
|
|
3829
|
+
features: "Unlimited projects\nPriority support\nAdvanced analytics",
|
|
3830
|
+
ctaLabel: "Start free trial",
|
|
3831
|
+
ctaHref: "#",
|
|
3832
|
+
featured: true
|
|
3833
|
+
},
|
|
3834
|
+
{
|
|
3835
|
+
name: "Enterprise",
|
|
3836
|
+
price: "Custom",
|
|
3837
|
+
period: "",
|
|
3838
|
+
description: "For large organisations.",
|
|
3839
|
+
features: "SSO & SAML\nDedicated success manager\nSLA",
|
|
3840
|
+
ctaLabel: "Contact sales",
|
|
3841
|
+
ctaHref: "#",
|
|
3842
|
+
featured: false
|
|
3843
|
+
}
|
|
3844
|
+
]
|
|
3845
|
+
},
|
|
3846
|
+
render: ({ id, plans }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3847
|
+
PricingTable,
|
|
3848
|
+
{
|
|
3849
|
+
plans: (plans ?? []).map(
|
|
3850
|
+
(p) => ({
|
|
3851
|
+
name: p.name,
|
|
3852
|
+
price: p.price,
|
|
3853
|
+
period: p.period,
|
|
3854
|
+
description: p.description,
|
|
3855
|
+
features: lines(p.features),
|
|
3856
|
+
cta: { label: p.ctaLabel, href: p.ctaHref },
|
|
3857
|
+
featured: p.featured
|
|
3858
|
+
})
|
|
3859
|
+
),
|
|
3860
|
+
testId: blockTestId(id)
|
|
3861
|
+
}
|
|
3862
|
+
)
|
|
3863
|
+
};
|
|
3864
|
+
const TabsBlock = {
|
|
3865
|
+
label: "Tabs",
|
|
3866
|
+
fields: {
|
|
3867
|
+
items: {
|
|
3868
|
+
type: "array",
|
|
3869
|
+
label: "Tabs",
|
|
3870
|
+
getItemSummary: (item) => item?.label || "Tab",
|
|
3871
|
+
defaultItemProps: { label: "Tab", content: "Tab content." },
|
|
3872
|
+
arrayFields: {
|
|
3873
|
+
label: textField("Label"),
|
|
3874
|
+
content: textareaField("Content")
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
},
|
|
3878
|
+
defaultProps: {
|
|
3879
|
+
items: [
|
|
3880
|
+
{ label: "Overview", content: "A quick overview of this section." },
|
|
3881
|
+
{ label: "Details", content: "The finer details go here." },
|
|
3882
|
+
{ label: "FAQ", content: "Answers to common questions." }
|
|
3883
|
+
]
|
|
3884
|
+
},
|
|
3885
|
+
render: ({ id, items }) => /* @__PURE__ */ jsxRuntime.jsx(Tabs, { items, testId: blockTestId(id) })
|
|
3886
|
+
};
|
|
3887
|
+
const AccordionBlock = {
|
|
3888
|
+
label: "Accordion",
|
|
3889
|
+
fields: {
|
|
3890
|
+
allowMultiple: boolRadio("Allow multiple open"),
|
|
3891
|
+
items: {
|
|
3892
|
+
type: "array",
|
|
3893
|
+
label: "Items",
|
|
3894
|
+
getItemSummary: (item) => item?.title || "Item",
|
|
3895
|
+
defaultItemProps: { title: "Section", content: "Section content." },
|
|
3896
|
+
arrayFields: {
|
|
3897
|
+
title: textField("Title"),
|
|
3898
|
+
content: textareaField("Content")
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
},
|
|
3902
|
+
defaultProps: {
|
|
3903
|
+
allowMultiple: false,
|
|
3904
|
+
items: [
|
|
3905
|
+
{ title: "What is included?", content: "Everything you need to get started." },
|
|
3906
|
+
{ title: "How does billing work?", content: "Monthly or annually, cancel anytime." }
|
|
3907
|
+
]
|
|
3908
|
+
},
|
|
3909
|
+
render: ({ id, allowMultiple, items }) => /* @__PURE__ */ jsxRuntime.jsx(Accordion, { allowMultiple, items, testId: blockTestId(id) })
|
|
3910
|
+
};
|
|
3911
|
+
const GalleryBlock = {
|
|
3912
|
+
label: "Gallery",
|
|
3913
|
+
fields: {
|
|
3914
|
+
columns: columnsField(["2", "3", "4"]),
|
|
3915
|
+
aspect: {
|
|
3916
|
+
type: "radio",
|
|
3917
|
+
label: "Aspect",
|
|
3918
|
+
options: [
|
|
3919
|
+
{ label: "1:1", value: "1:1" },
|
|
3920
|
+
{ label: "4:3", value: "4:3" },
|
|
3921
|
+
{ label: "16:9", value: "16:9" }
|
|
3922
|
+
]
|
|
3923
|
+
},
|
|
3924
|
+
images: {
|
|
3925
|
+
type: "array",
|
|
3926
|
+
label: "Images",
|
|
3927
|
+
getItemSummary: (item) => item?.alt || "Image",
|
|
3928
|
+
defaultItemProps: { src: "", alt: "Image", caption: "" },
|
|
3929
|
+
arrayFields: {
|
|
3930
|
+
src: imageField("Image"),
|
|
3931
|
+
alt: { type: "text", label: "Alt text" },
|
|
3932
|
+
caption: { type: "text", label: "Caption" }
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
},
|
|
3936
|
+
defaultProps: {
|
|
3937
|
+
columns: "3",
|
|
3938
|
+
aspect: "1:1",
|
|
3939
|
+
images: [
|
|
3940
|
+
{ src: "", alt: "Image one", caption: "" },
|
|
3941
|
+
{ src: "", alt: "Image two", caption: "" },
|
|
3942
|
+
{ src: "", alt: "Image three", caption: "" }
|
|
3943
|
+
]
|
|
3944
|
+
},
|
|
3945
|
+
render: ({ id, columns, aspect, images }) => /* @__PURE__ */ jsxRuntime.jsx(Gallery, { columns, aspect, images, testId: blockTestId(id) })
|
|
3946
|
+
};
|
|
3947
|
+
const VideoBlock = {
|
|
3948
|
+
label: "Video",
|
|
3949
|
+
fields: {
|
|
3950
|
+
url: { type: "text", label: "Video URL (YouTube/Vimeo/Loom or file)" },
|
|
3951
|
+
title: { type: "text", label: "Title" },
|
|
3952
|
+
aspect: {
|
|
3953
|
+
type: "radio",
|
|
3954
|
+
label: "Aspect",
|
|
3955
|
+
options: [
|
|
3956
|
+
{ label: "16:9", value: "16:9" },
|
|
3957
|
+
{ label: "4:3", value: "4:3" },
|
|
3958
|
+
{ label: "1:1", value: "1:1" }
|
|
3959
|
+
]
|
|
3960
|
+
},
|
|
3961
|
+
rounded: boolRadio("Rounded")
|
|
3962
|
+
},
|
|
3963
|
+
defaultProps: { url: "", title: "", aspect: "16:9", rounded: true },
|
|
3964
|
+
render: ({ id, url, title, aspect, rounded }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3965
|
+
VideoEmbed,
|
|
3966
|
+
{
|
|
3967
|
+
url,
|
|
3968
|
+
title,
|
|
3969
|
+
aspect,
|
|
3970
|
+
rounded,
|
|
3971
|
+
testId: blockTestId(id)
|
|
3972
|
+
}
|
|
3973
|
+
)
|
|
3974
|
+
};
|
|
3975
|
+
const ContactFormBlock = {
|
|
3976
|
+
label: "Contact form",
|
|
3977
|
+
fields: {
|
|
3978
|
+
submitLabel: { type: "text", label: "Submit label" },
|
|
3979
|
+
action: { type: "text", label: "POST endpoint (optional)" },
|
|
3980
|
+
successMessage: textareaField("Success message"),
|
|
3981
|
+
fields: {
|
|
3982
|
+
type: "array",
|
|
3983
|
+
label: "Fields",
|
|
3984
|
+
getItemSummary: (item) => item?.label || "Field",
|
|
3985
|
+
defaultItemProps: { name: "field", label: "Field", type: "text", required: false },
|
|
3986
|
+
arrayFields: {
|
|
3987
|
+
name: { type: "text", label: "Name attribute" },
|
|
3988
|
+
label: textField("Label"),
|
|
3989
|
+
type: {
|
|
3990
|
+
type: "select",
|
|
3991
|
+
label: "Type",
|
|
3992
|
+
options: [
|
|
3993
|
+
{ label: "Text", value: "text" },
|
|
3994
|
+
{ label: "Email", value: "email" },
|
|
3995
|
+
{ label: "Phone", value: "tel" },
|
|
3996
|
+
{ label: "Multi-line", value: "textarea" }
|
|
3997
|
+
]
|
|
3998
|
+
},
|
|
3999
|
+
required: boolRadio("Required")
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
},
|
|
4003
|
+
defaultProps: {
|
|
4004
|
+
submitLabel: "Send",
|
|
4005
|
+
action: "",
|
|
4006
|
+
successMessage: "Thanks \u2014 we\u2019ll be in touch shortly.",
|
|
4007
|
+
fields: [
|
|
4008
|
+
{ name: "name", label: "Name", type: "text", required: true },
|
|
4009
|
+
{ name: "email", label: "Email", type: "email", required: true },
|
|
4010
|
+
{ name: "message", label: "Message", type: "textarea", required: true }
|
|
4011
|
+
]
|
|
4012
|
+
},
|
|
4013
|
+
render: ({ id, submitLabel, action, successMessage, fields }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4014
|
+
ContactForm,
|
|
4015
|
+
{
|
|
4016
|
+
submitLabel,
|
|
4017
|
+
action: action || void 0,
|
|
4018
|
+
successMessage,
|
|
4019
|
+
fields,
|
|
4020
|
+
testId: blockTestId(id)
|
|
4021
|
+
}
|
|
4022
|
+
)
|
|
4023
|
+
};
|
|
4024
|
+
const BannerBlock = {
|
|
4025
|
+
label: "Banner",
|
|
4026
|
+
fields: {
|
|
4027
|
+
text: textField("Text"),
|
|
4028
|
+
cta: ctaField("CTA"),
|
|
4029
|
+
tone: {
|
|
4030
|
+
type: "radio",
|
|
4031
|
+
label: "Tone",
|
|
4032
|
+
options: [
|
|
4033
|
+
{ label: "Primary", value: "primary" },
|
|
4034
|
+
{ label: "Muted", value: "muted" },
|
|
4035
|
+
{ label: "Dark", value: "ink" }
|
|
4036
|
+
]
|
|
4037
|
+
}
|
|
4038
|
+
},
|
|
4039
|
+
defaultProps: {
|
|
4040
|
+
text: "We just shipped something new.",
|
|
4041
|
+
cta: { label: "Read more", href: "#" },
|
|
4042
|
+
tone: "primary"
|
|
4043
|
+
},
|
|
4044
|
+
render: ({ id, text, cta, tone }) => /* @__PURE__ */ jsxRuntime.jsx(Banner, { text, cta, tone, testId: blockTestId(id) })
|
|
4045
|
+
};
|
|
4046
|
+
const MediaTextBlock = {
|
|
4047
|
+
label: "Media & text",
|
|
4048
|
+
fields: {
|
|
4049
|
+
eyebrow: textField("Eyebrow"),
|
|
4050
|
+
title: textField("Title"),
|
|
4051
|
+
body: textareaField("Body", void 0, "lead"),
|
|
4052
|
+
imageUrl: imageField("Image"),
|
|
4053
|
+
mediaSide: {
|
|
4054
|
+
type: "radio",
|
|
4055
|
+
label: "Media side",
|
|
4056
|
+
options: [
|
|
4057
|
+
{ label: "Left", value: "left" },
|
|
4058
|
+
{ label: "Right", value: "right" }
|
|
4059
|
+
]
|
|
4060
|
+
},
|
|
4061
|
+
cta: ctaField("CTA")
|
|
4062
|
+
},
|
|
4063
|
+
defaultProps: {
|
|
4064
|
+
eyebrow: "",
|
|
4065
|
+
title: "A closer look at what we do.",
|
|
4066
|
+
body: "Pair a compelling visual with the copy that explains it \u2014 the classic split section, on any theme.",
|
|
4067
|
+
imageUrl: "",
|
|
4068
|
+
mediaSide: "left",
|
|
4069
|
+
cta: { label: "Learn more", href: "#" }
|
|
4070
|
+
},
|
|
4071
|
+
render: ({ id, eyebrow, title, body, imageUrl, mediaSide, cta }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4072
|
+
MediaText,
|
|
4073
|
+
{
|
|
4074
|
+
eyebrow,
|
|
4075
|
+
title,
|
|
4076
|
+
body,
|
|
4077
|
+
imageUrl,
|
|
4078
|
+
mediaSide,
|
|
4079
|
+
cta,
|
|
4080
|
+
testId: blockTestId(id)
|
|
4081
|
+
}
|
|
4082
|
+
)
|
|
4083
|
+
};
|
|
4084
|
+
const TeamGridBlock = {
|
|
4085
|
+
label: "Team",
|
|
4086
|
+
fields: {
|
|
4087
|
+
columns: columnsField(["2", "3", "4"]),
|
|
4088
|
+
members: {
|
|
4089
|
+
type: "array",
|
|
4090
|
+
label: "Members",
|
|
4091
|
+
getItemSummary: (item) => item?.name || "Member",
|
|
4092
|
+
defaultItemProps: { name: "New member", role: "Role", bio: "", avatarUrl: "" },
|
|
4093
|
+
arrayFields: {
|
|
4094
|
+
name: textField("Name"),
|
|
4095
|
+
role: { type: "text", label: "Role" },
|
|
4096
|
+
bio: textareaField("Bio"),
|
|
4097
|
+
avatarUrl: { type: "text", label: "Avatar URL" }
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
},
|
|
4101
|
+
defaultProps: {
|
|
4102
|
+
columns: "3",
|
|
4103
|
+
members: [
|
|
4104
|
+
{ name: "Alex Morgan", role: "Founder & CEO", bio: "", avatarUrl: "" },
|
|
4105
|
+
{ name: "Sam Rivera", role: "Head of Product", bio: "", avatarUrl: "" },
|
|
4106
|
+
{ name: "Jordan Lee", role: "Lead Engineer", bio: "", avatarUrl: "" }
|
|
4107
|
+
]
|
|
4108
|
+
},
|
|
4109
|
+
render: ({ id, columns, members }) => /* @__PURE__ */ jsxRuntime.jsx(TeamGrid, { columns, members, testId: blockTestId(id) })
|
|
4110
|
+
};
|
|
4111
|
+
const StepsBlock = {
|
|
4112
|
+
label: "Steps",
|
|
4113
|
+
fields: {
|
|
4114
|
+
variant: {
|
|
4115
|
+
type: "radio",
|
|
4116
|
+
label: "Style",
|
|
4117
|
+
options: [
|
|
4118
|
+
{ label: "Numbered", value: "numbered" },
|
|
4119
|
+
{ label: "Timeline", value: "timeline" }
|
|
4120
|
+
]
|
|
4121
|
+
},
|
|
4122
|
+
steps: {
|
|
4123
|
+
type: "array",
|
|
4124
|
+
label: "Steps",
|
|
4125
|
+
getItemSummary: (item) => item?.title || "Step",
|
|
4126
|
+
defaultItemProps: { title: "Step", description: "Describe this step." },
|
|
4127
|
+
arrayFields: {
|
|
4128
|
+
title: textField("Title"),
|
|
4129
|
+
description: textareaField("Description")
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
},
|
|
4133
|
+
defaultProps: {
|
|
4134
|
+
variant: "numbered",
|
|
4135
|
+
steps: [
|
|
4136
|
+
{ title: "Sign up", description: "Create your account in seconds." },
|
|
4137
|
+
{ title: "Configure", description: "Set things up to match your workflow." },
|
|
4138
|
+
{ title: "Launch", description: "Go live and start seeing results." }
|
|
4139
|
+
]
|
|
4140
|
+
},
|
|
4141
|
+
render: ({ id, variant, steps }) => /* @__PURE__ */ jsxRuntime.jsx(Steps, { variant, steps, testId: blockTestId(id) })
|
|
4142
|
+
};
|
|
4143
|
+
const TestimonialGridBlock = {
|
|
4144
|
+
label: "Testimonial grid",
|
|
4145
|
+
fields: {
|
|
4146
|
+
columns: columnsField(["2", "3"]),
|
|
4147
|
+
items: {
|
|
4148
|
+
type: "array",
|
|
4149
|
+
label: "Testimonials",
|
|
4150
|
+
getItemSummary: (item) => item?.author || "Testimonial",
|
|
4151
|
+
defaultItemProps: { quote: "A great quote.", author: "Author", role: "", avatarUrl: "" },
|
|
4152
|
+
arrayFields: {
|
|
4153
|
+
quote: textareaField("Quote"),
|
|
4154
|
+
author: textField("Author"),
|
|
4155
|
+
role: { type: "text", label: "Role" },
|
|
4156
|
+
avatarUrl: { type: "text", label: "Avatar URL" }
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
},
|
|
4160
|
+
defaultProps: {
|
|
4161
|
+
columns: "3",
|
|
4162
|
+
items: [
|
|
4163
|
+
{
|
|
4164
|
+
quote: "This changed how our whole team works.",
|
|
4165
|
+
author: "Alex Morgan",
|
|
4166
|
+
role: "CTO, Northwind",
|
|
4167
|
+
avatarUrl: ""
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
quote: "The best tool we adopted this year.",
|
|
4171
|
+
author: "Sam Rivera",
|
|
4172
|
+
role: "Ops Lead, Acme",
|
|
4173
|
+
avatarUrl: ""
|
|
4174
|
+
},
|
|
4175
|
+
{
|
|
4176
|
+
quote: "Simple, fast, and it just works.",
|
|
4177
|
+
author: "Jordan Lee",
|
|
4178
|
+
role: "Founder, Globex",
|
|
4179
|
+
avatarUrl: ""
|
|
4180
|
+
}
|
|
4181
|
+
]
|
|
4182
|
+
},
|
|
4183
|
+
render: ({ id, columns, items }) => /* @__PURE__ */ jsxRuntime.jsx(TestimonialGrid, { columns, items, testId: blockTestId(id) })
|
|
4184
|
+
};
|
|
4185
|
+
return {
|
|
4186
|
+
Pricing: PricingBlock,
|
|
4187
|
+
Tabs: TabsBlock,
|
|
4188
|
+
Accordion: AccordionBlock,
|
|
4189
|
+
Gallery: GalleryBlock,
|
|
4190
|
+
Video: VideoBlock,
|
|
4191
|
+
ContactForm: ContactFormBlock,
|
|
4192
|
+
Banner: BannerBlock,
|
|
4193
|
+
MediaText: MediaTextBlock,
|
|
4194
|
+
Team: TeamGridBlock,
|
|
4195
|
+
Steps: StepsBlock,
|
|
4196
|
+
TestimonialGrid: TestimonialGridBlock
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
2834
4199
|
function createArbiBlocksConfig(opts = {}) {
|
|
2835
|
-
const kit = createBlockKit(opts.aiFields);
|
|
4200
|
+
const kit = createBlockKit(opts.aiFields, opts.imageField, opts.eyebrowClassName);
|
|
2836
4201
|
const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
|
|
2837
4202
|
const SectionHeadingBlock = {
|
|
2838
4203
|
label: "Section heading",
|
|
@@ -2851,7 +4216,17 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
2851
4216
|
invert: boolRadio("Invert (on dark)")
|
|
2852
4217
|
},
|
|
2853
4218
|
defaultProps: { title: "Section title", align: "left", invert: false },
|
|
2854
|
-
render: ({ id, title, eyebrow, lead, align, invert }) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4219
|
+
render: ({ id, title, eyebrow, lead, align, invert }) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4220
|
+
SectionHeading,
|
|
4221
|
+
{
|
|
4222
|
+
title,
|
|
4223
|
+
eyebrow,
|
|
4224
|
+
lead,
|
|
4225
|
+
align,
|
|
4226
|
+
invert,
|
|
4227
|
+
eyebrowClassName: kit.eyebrowClassName
|
|
4228
|
+
}
|
|
4229
|
+
) })
|
|
2855
4230
|
};
|
|
2856
4231
|
const PageHeaderBlock = {
|
|
2857
4232
|
label: "Page header",
|
|
@@ -2901,9 +4276,10 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
2901
4276
|
type: "radio",
|
|
2902
4277
|
label: "Accent",
|
|
2903
4278
|
options: [
|
|
2904
|
-
{ label: "
|
|
2905
|
-
{ label: "
|
|
2906
|
-
{ label: "
|
|
4279
|
+
{ label: "Default", value: "default" },
|
|
4280
|
+
{ label: "Primary", value: "primary" },
|
|
4281
|
+
{ label: "Success", value: "success" },
|
|
4282
|
+
{ label: "Muted", value: "muted" }
|
|
2907
4283
|
]
|
|
2908
4284
|
},
|
|
2909
4285
|
delta: {
|
|
@@ -2924,7 +4300,7 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
2924
4300
|
},
|
|
2925
4301
|
icon: iconField()
|
|
2926
4302
|
},
|
|
2927
|
-
defaultProps: { label: "Metric", value: "0", accent: "
|
|
4303
|
+
defaultProps: { label: "Metric", value: "0", accent: "default" },
|
|
2928
4304
|
render: ({ id, label, value, hint, accent, delta, icon }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2929
4305
|
MetricCard,
|
|
2930
4306
|
{
|
|
@@ -3027,12 +4403,39 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3027
4403
|
options: [
|
|
3028
4404
|
{ label: "Default", value: "default" },
|
|
3029
4405
|
{ label: "Muted", value: "muted" },
|
|
3030
|
-
{ label: "
|
|
4406
|
+
{ label: "Dark", value: "ink" }
|
|
3031
4407
|
]
|
|
3032
|
-
}
|
|
4408
|
+
},
|
|
4409
|
+
padding: {
|
|
4410
|
+
type: "radio",
|
|
4411
|
+
label: "Padding",
|
|
4412
|
+
options: [
|
|
4413
|
+
{ label: "None", value: "none" },
|
|
4414
|
+
{ label: "S", value: "sm" },
|
|
4415
|
+
{ label: "M", value: "md" },
|
|
4416
|
+
{ label: "L", value: "lg" }
|
|
4417
|
+
]
|
|
4418
|
+
},
|
|
4419
|
+
maxWidth: {
|
|
4420
|
+
type: "radio",
|
|
4421
|
+
label: "Width",
|
|
4422
|
+
options: [
|
|
4423
|
+
{ label: "Narrow", value: "narrow" },
|
|
4424
|
+
{ label: "Default", value: "default" },
|
|
4425
|
+
{ label: "Wide", value: "wide" },
|
|
4426
|
+
{ label: "Full", value: "full" }
|
|
4427
|
+
]
|
|
4428
|
+
},
|
|
4429
|
+
texture: boolRadio("Texture")
|
|
4430
|
+
},
|
|
4431
|
+
defaultProps: {
|
|
4432
|
+
tone: "default",
|
|
4433
|
+
padding: "md",
|
|
4434
|
+
maxWidth: "default",
|
|
4435
|
+
texture: false,
|
|
4436
|
+
children: []
|
|
3033
4437
|
},
|
|
3034
|
-
|
|
3035
|
-
render: ({ id, tone, children: Children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsxRuntime.jsx(Section, { tone, children: /* @__PURE__ */ jsxRuntime.jsx(Children, {}) }) })
|
|
4438
|
+
render: ({ id, tone, padding, maxWidth, texture, children: Children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsxRuntime.jsx(Section, { tone, padding, maxWidth, texture, children: /* @__PURE__ */ jsxRuntime.jsx(Children, {}) }) })
|
|
3036
4439
|
};
|
|
3037
4440
|
const CardBlock = {
|
|
3038
4441
|
label: "Card",
|
|
@@ -3108,6 +4511,7 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3108
4511
|
render: ({ id }) => /* @__PURE__ */ jsxRuntime.jsx(react.Separator, { "data-testid": blockTestId(id) })
|
|
3109
4512
|
};
|
|
3110
4513
|
const website = createWebsiteBlocks(kit);
|
|
4514
|
+
const site = createSiteBlocks(kit);
|
|
3111
4515
|
const components = {
|
|
3112
4516
|
SectionHeading: SectionHeadingBlock,
|
|
3113
4517
|
PageHeader: PageHeaderBlock,
|
|
@@ -3119,7 +4523,8 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3119
4523
|
Button: ButtonBlock,
|
|
3120
4524
|
Badge: BadgeBlock,
|
|
3121
4525
|
Separator: SeparatorBlock,
|
|
3122
|
-
...website
|
|
4526
|
+
...website,
|
|
4527
|
+
...site
|
|
3123
4528
|
};
|
|
3124
4529
|
const config = {
|
|
3125
4530
|
categories: {
|
|
@@ -3134,16 +4539,24 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3134
4539
|
"FeatureGrid",
|
|
3135
4540
|
"StatGroup",
|
|
3136
4541
|
"Testimonial",
|
|
4542
|
+
"TestimonialGrid",
|
|
3137
4543
|
"CTABanner",
|
|
4544
|
+
"Banner",
|
|
3138
4545
|
"LogoCloud",
|
|
3139
|
-
"FAQ"
|
|
4546
|
+
"FAQ",
|
|
4547
|
+
"MediaText",
|
|
4548
|
+
"Steps",
|
|
4549
|
+
"Team"
|
|
3140
4550
|
]
|
|
3141
4551
|
},
|
|
3142
4552
|
Content: {
|
|
3143
4553
|
title: "Content",
|
|
3144
4554
|
components: ["SectionHeading", "PageHeader", "EmptyState", "RichText", "Callout", "List"]
|
|
3145
4555
|
},
|
|
3146
|
-
|
|
4556
|
+
Interactive: { title: "Interactive", components: ["Tabs", "Accordion"] },
|
|
4557
|
+
Media: { title: "Media", components: ["Image", "Avatar", "Gallery", "Video"] },
|
|
4558
|
+
Commerce: { title: "Commerce", components: ["Pricing"] },
|
|
4559
|
+
Forms: { title: "Forms", components: ["ContactForm"] },
|
|
3147
4560
|
Navigation: { title: "Navigation", components: ["Navbar", "Footer"] },
|
|
3148
4561
|
Data: { title: "Data", components: ["MetricCard", "DataTable"] },
|
|
3149
4562
|
Primitives: { title: "Primitives", components: ["Button", "Badge", "Separator"] }
|
|
@@ -3153,8 +4566,8 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3153
4566
|
render: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3154
4567
|
"div",
|
|
3155
4568
|
{
|
|
3156
|
-
className: "bg-background text-foreground font-sans",
|
|
3157
|
-
"data-testid": "arbi-blocks-page-root",
|
|
4569
|
+
className: react.cn("bg-background text-foreground font-sans", opts.root?.className),
|
|
4570
|
+
"data-testid": opts.root?.testId ?? "arbi-blocks-page-root",
|
|
3158
4571
|
children
|
|
3159
4572
|
}
|
|
3160
4573
|
)
|
|
@@ -3394,7 +4807,7 @@ function ModuleManager({ store, testIdPrefix = "builder" }) {
|
|
|
3394
4807
|
] }),
|
|
3395
4808
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
3396
4809
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3397
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-
|
|
4810
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: m.label }),
|
|
3398
4811
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: m.to })
|
|
3399
4812
|
] }),
|
|
3400
4813
|
m.arbi && /* @__PURE__ */ jsxRuntime.jsxs(react.Badge, { variant: "accent", className: "text-[10px]", children: [
|
|
@@ -3465,7 +4878,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3465
4878
|
"data-testid": `${testIdPrefix}-agents-configs`,
|
|
3466
4879
|
children: [
|
|
3467
4880
|
/* @__PURE__ */ jsxRuntime.jsx(react.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.CardTitle, { className: "flex items-center gap-2 text-base", children: [
|
|
3468
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.BrainCircuit, { className: "size-4 text-
|
|
4881
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.BrainCircuit, { className: "size-4 text-primary" }),
|
|
3469
4882
|
" Behaviour configurations",
|
|
3470
4883
|
/* @__PURE__ */ jsxRuntime.jsx(react.Badge, { variant: "secondary", className: "ml-1", children: versions.length })
|
|
3471
4884
|
] }) }),
|
|
@@ -3487,7 +4900,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3487
4900
|
}
|
|
3488
4901
|
),
|
|
3489
4902
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3490
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-
|
|
4903
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: v.title ?? "Untitled behaviour" }),
|
|
3491
4904
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: v.external_id })
|
|
3492
4905
|
] }),
|
|
3493
4906
|
/* @__PURE__ */ jsxRuntime.jsx(ConfigModelBadge, { configId: v.external_id, testIdPrefix })
|
|
@@ -3501,7 +4914,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3501
4914
|
),
|
|
3502
4915
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Card, { variant: "elevated", className: "rounded-xl", "data-testid": `${testIdPrefix}-agents-agents`, children: [
|
|
3503
4916
|
/* @__PURE__ */ jsxRuntime.jsx(react.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.CardTitle, { className: "flex items-center gap-2 text-base", children: [
|
|
3504
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bot, { className: "size-4 text-
|
|
4917
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bot, { className: "size-4 text-primary" }),
|
|
3505
4918
|
" Agents",
|
|
3506
4919
|
/* @__PURE__ */ jsxRuntime.jsx(react.Badge, { variant: "secondary", className: "ml-1", children: agents.length })
|
|
3507
4920
|
] }) }),
|
|
@@ -3523,7 +4936,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3523
4936
|
}
|
|
3524
4937
|
),
|
|
3525
4938
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3526
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-
|
|
4939
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: (a.given_name ?? "Agent").replace(/-/g, " ") }),
|
|
3527
4940
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: a.external_id })
|
|
3528
4941
|
] })
|
|
3529
4942
|
]
|
|
@@ -3548,7 +4961,7 @@ function ExportPanel({
|
|
|
3548
4961
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Card, { variant: "elevated", className: "rounded-xl", "data-testid": `${testIdPrefix}-export-panel`, children: [
|
|
3549
4962
|
/* @__PURE__ */ jsxRuntime.jsx(react.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.CardTitle, { className: "flex items-center justify-between text-base", children: [
|
|
3550
4963
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
3551
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileJson, { className: "size-4 text-
|
|
4964
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileJson, { className: "size-4 text-primary" }),
|
|
3552
4965
|
" Vertical config"
|
|
3553
4966
|
] }),
|
|
3554
4967
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -3568,7 +4981,7 @@ function ExportPanel({
|
|
|
3568
4981
|
/* @__PURE__ */ jsxRuntime.jsx(react.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3569
4982
|
"pre",
|
|
3570
4983
|
{
|
|
3571
|
-
className: "scrollbar-slim max-h-[520px] overflow-auto rounded-md bg-
|
|
4984
|
+
className: "scrollbar-slim max-h-[520px] overflow-auto rounded-md bg-foreground p-4 font-mono text-[11px] leading-relaxed text-background",
|
|
3572
4985
|
"data-testid": `${testIdPrefix}-export-preview`,
|
|
3573
4986
|
children: json
|
|
3574
4987
|
}
|
|
@@ -3666,13 +5079,18 @@ Object.defineProperty(exports, "toRedlineMarkdown", {
|
|
|
3666
5079
|
enumerable: true,
|
|
3667
5080
|
get: function () { return react.toRedlineMarkdown; }
|
|
3668
5081
|
});
|
|
5082
|
+
exports.ASSET_REF_PREFIX = ASSET_REF_PREFIX;
|
|
5083
|
+
exports.Accordion = Accordion;
|
|
3669
5084
|
exports.AgentsPanel = AgentsPanel;
|
|
5085
|
+
exports.AssetResolverProvider = AssetResolverProvider;
|
|
3670
5086
|
exports.AvatarBlock = AvatarBlock;
|
|
5087
|
+
exports.Banner = Banner;
|
|
3671
5088
|
exports.BlockImage = BlockImage;
|
|
3672
5089
|
exports.CTABanner = CTABanner;
|
|
3673
5090
|
exports.Callout = Callout;
|
|
3674
5091
|
exports.Columns = Columns;
|
|
3675
5092
|
exports.ConnectionProvider = ConnectionProvider;
|
|
5093
|
+
exports.ContactForm = ContactForm;
|
|
3676
5094
|
exports.Container = Container;
|
|
3677
5095
|
exports.DataTable = DataTable;
|
|
3678
5096
|
exports.DataTableBlock = DataTableBlock;
|
|
@@ -3680,37 +5098,50 @@ exports.EmptyState = EmptyState;
|
|
|
3680
5098
|
exports.FAQ = FAQ;
|
|
3681
5099
|
exports.FeatureGrid = FeatureGrid;
|
|
3682
5100
|
exports.Footer = Footer;
|
|
5101
|
+
exports.Gallery = Gallery;
|
|
3683
5102
|
exports.GridView = GridView;
|
|
3684
5103
|
exports.Hero = Hero;
|
|
3685
5104
|
exports.LegalArbiProvider = LegalArbiProvider;
|
|
3686
5105
|
exports.ListBlock = ListBlock;
|
|
3687
5106
|
exports.LogoCloud = LogoCloud;
|
|
5107
|
+
exports.MediaText = MediaText;
|
|
3688
5108
|
exports.MetricCard = MetricCard;
|
|
3689
5109
|
exports.ModuleManager = ModuleManager;
|
|
3690
5110
|
exports.Navbar = Navbar;
|
|
3691
5111
|
exports.PageHeader = PageHeader;
|
|
3692
5112
|
exports.PageRenderer = PageRenderer;
|
|
5113
|
+
exports.PricingTable = PricingTable;
|
|
3693
5114
|
exports.RichTextBlock = RichTextBlock;
|
|
3694
5115
|
exports.Section = Section;
|
|
3695
5116
|
exports.SectionHeading = SectionHeading;
|
|
3696
5117
|
exports.Spacer = Spacer;
|
|
3697
5118
|
exports.StatGroup = StatGroup;
|
|
5119
|
+
exports.Steps = Steps;
|
|
3698
5120
|
exports.StudioEditor = StudioEditor;
|
|
3699
5121
|
exports.THEME_STYLE_ID = THEME_STYLE_ID;
|
|
5122
|
+
exports.Tabs = Tabs;
|
|
5123
|
+
exports.TeamGrid = TeamGrid;
|
|
3700
5124
|
exports.Testimonial = Testimonial;
|
|
5125
|
+
exports.TestimonialGrid = TestimonialGrid;
|
|
3701
5126
|
exports.ThemeEditor = ThemeEditor;
|
|
3702
5127
|
exports.Toolbar = Toolbar;
|
|
3703
5128
|
exports.VerticalBuilder = VerticalBuilder;
|
|
5129
|
+
exports.VideoEmbed = VideoEmbed;
|
|
3704
5130
|
exports.applyStoredTheme = applyStoredTheme;
|
|
3705
5131
|
exports.applyThemeVars = applyThemeVars;
|
|
5132
|
+
exports.asAssetRef = asAssetRef;
|
|
5133
|
+
exports.assetRefId = assetRefId;
|
|
3706
5134
|
exports.clearThemeVars = clearThemeVars;
|
|
3707
5135
|
exports.createAgentSelectionStore = createAgentSelectionStore;
|
|
3708
5136
|
exports.createAiFields = createAiFields;
|
|
3709
5137
|
exports.createArbiBlocksConfig = createArbiBlocksConfig;
|
|
5138
|
+
exports.createArbiMaterializer = createArbiMaterializer;
|
|
3710
5139
|
exports.createBlockKit = createBlockKit;
|
|
5140
|
+
exports.createImageField = createImageField;
|
|
3711
5141
|
exports.createModuleRegistry = createModuleRegistry;
|
|
3712
5142
|
exports.createModuleStore = createModuleStore;
|
|
3713
5143
|
exports.createPromptLibrary = createPromptLibrary;
|
|
5144
|
+
exports.createSiteBlocks = createSiteBlocks;
|
|
3714
5145
|
exports.createStudioPersistence = createStudioPersistence;
|
|
3715
5146
|
exports.createThemeStore = createThemeStore;
|
|
3716
5147
|
exports.createVerticalExport = createVerticalExport;
|
|
@@ -3723,13 +5154,18 @@ exports.gbp = gbp;
|
|
|
3723
5154
|
exports.hrs = hrs;
|
|
3724
5155
|
exports.iconMap = iconMap;
|
|
3725
5156
|
exports.initials = initials;
|
|
5157
|
+
exports.isAssetRef = isAssetRef;
|
|
3726
5158
|
exports.isAuthenticated = isAuthenticated;
|
|
5159
|
+
exports.materializePageData = materializePageData;
|
|
3727
5160
|
exports.matterToContext = matterToContext;
|
|
3728
5161
|
exports.pct = pct;
|
|
3729
5162
|
exports.tid = tid;
|
|
5163
|
+
exports.toEmbedUrl = toEmbedUrl;
|
|
3730
5164
|
exports.toHslTriplet = toHslTriplet;
|
|
5165
|
+
exports.useAssetResolverActive = useAssetResolverActive;
|
|
3731
5166
|
exports.useConnection = useConnection;
|
|
3732
5167
|
exports.useFirmAiTask = useFirmAiTask;
|
|
5168
|
+
exports.useImageGen = useImageGen;
|
|
3733
5169
|
exports.useSemanticSearch = useSemanticSearch;
|
|
3734
5170
|
exports.useWorkspaceDocs = useWorkspaceDocs;
|
|
3735
5171
|
//# sourceMappingURL=index.cjs.map
|