@arbidocs/blocks 0.3.110 → 0.3.112
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 +1534 -119
- 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 +1515 -123
- 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,33 @@ 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 AssetThumb({
|
|
1455
|
+
docId,
|
|
1456
|
+
alt,
|
|
1457
|
+
className,
|
|
1458
|
+
testId
|
|
1459
|
+
}) {
|
|
1460
|
+
const { data } = react.useThumbnails([docId]);
|
|
1461
|
+
const src = data?.[docId];
|
|
1462
|
+
return /* @__PURE__ */ jsxRuntime.jsx("img", { src: src ?? void 0, alt: alt ?? "", className, "data-testid": testId });
|
|
1463
|
+
}
|
|
1427
1464
|
var ASPECT = {
|
|
1428
1465
|
"16:9": "aspect-video",
|
|
1429
1466
|
"4:3": "aspect-[4/3]",
|
|
@@ -1442,11 +1479,28 @@ function BlockImage({
|
|
|
1442
1479
|
alt = "",
|
|
1443
1480
|
aspect = "16:9",
|
|
1444
1481
|
rounded = "lg",
|
|
1482
|
+
framed,
|
|
1445
1483
|
className,
|
|
1446
1484
|
testId
|
|
1447
1485
|
}) {
|
|
1448
|
-
const shape = react.cn(
|
|
1449
|
-
|
|
1486
|
+
const shape = react.cn(
|
|
1487
|
+
ASPECT[aspect] ?? ASPECT["16:9"],
|
|
1488
|
+
ROUNDED[rounded] ?? ROUNDED.lg,
|
|
1489
|
+
framed && "ring-1 ring-border shadow-xl"
|
|
1490
|
+
);
|
|
1491
|
+
const canResolve = useAssetResolverActive();
|
|
1492
|
+
if (canResolve && isAssetRef(src)) {
|
|
1493
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1494
|
+
AssetThumb,
|
|
1495
|
+
{
|
|
1496
|
+
docId: assetRefId(src),
|
|
1497
|
+
alt,
|
|
1498
|
+
className: react.cn("w-full object-cover", shape, className),
|
|
1499
|
+
testId
|
|
1500
|
+
}
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
if (src && !isAssetRef(src)) {
|
|
1450
1504
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1451
1505
|
"img",
|
|
1452
1506
|
{
|
|
@@ -1478,38 +1532,82 @@ var TONE_SURFACE = {
|
|
|
1478
1532
|
muted: "bg-muted text-foreground",
|
|
1479
1533
|
ink: "bg-foreground text-background"
|
|
1480
1534
|
};
|
|
1535
|
+
function renderTitle(title, emphasis) {
|
|
1536
|
+
if (!emphasis || !title.includes(emphasis)) return title;
|
|
1537
|
+
const parts = title.split(emphasis);
|
|
1538
|
+
return parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1539
|
+
part,
|
|
1540
|
+
i < parts.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary", children: emphasis })
|
|
1541
|
+
] }, i));
|
|
1542
|
+
}
|
|
1481
1543
|
function Hero({
|
|
1482
1544
|
eyebrow,
|
|
1483
1545
|
title,
|
|
1546
|
+
emphasis,
|
|
1484
1547
|
subtitle,
|
|
1485
1548
|
primaryCta,
|
|
1486
1549
|
secondaryCta,
|
|
1487
1550
|
align = "left",
|
|
1488
1551
|
tone = "default",
|
|
1489
1552
|
imageUrl,
|
|
1553
|
+
backgroundImage,
|
|
1554
|
+
overlay = true,
|
|
1555
|
+
eyebrowClassName,
|
|
1490
1556
|
testId
|
|
1491
1557
|
}) {
|
|
1492
|
-
const
|
|
1493
|
-
const
|
|
1494
|
-
const
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1558
|
+
const onDark = Boolean(backgroundImage) || tone === "ink";
|
|
1559
|
+
const subtle = onDark ? "text-background/70" : "text-muted-foreground";
|
|
1560
|
+
const eyebrowClass = eyebrowClassName ?? (onDark ? "text-background/60" : "text-muted-foreground");
|
|
1561
|
+
const centered = align === "center" || Boolean(backgroundImage);
|
|
1562
|
+
const heading = renderTitle(title, emphasis);
|
|
1563
|
+
const copy = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1564
|
+
"div",
|
|
1565
|
+
{
|
|
1566
|
+
className: react.cn(
|
|
1567
|
+
"max-w-2xl",
|
|
1568
|
+
centered && "mx-auto text-center",
|
|
1569
|
+
backgroundImage && "text-background"
|
|
1570
|
+
),
|
|
1571
|
+
children: [
|
|
1572
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: react.cn("mb-4 text-sm font-semibold uppercase tracking-wider", eyebrowClass), children: eyebrow }),
|
|
1573
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl font-semibold tracking-tight text-balance sm:text-5xl", children: heading }),
|
|
1574
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: react.cn("mt-6 text-lg leading-relaxed", subtle), children: subtitle }),
|
|
1575
|
+
(primaryCta?.label || secondaryCta?.label) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1576
|
+
"div",
|
|
1577
|
+
{
|
|
1578
|
+
className: react.cn("mt-8 flex flex-col gap-3 sm:flex-row", centered && "sm:justify-center"),
|
|
1579
|
+
children: [
|
|
1580
|
+
primaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, size: "lg", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: primaryCta.href || "#", children: [
|
|
1581
|
+
primaryCta.label,
|
|
1582
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
1583
|
+
] }) }),
|
|
1584
|
+
secondaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, size: "lg", variant: "outline", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label }) })
|
|
1585
|
+
]
|
|
1586
|
+
}
|
|
1587
|
+
)
|
|
1588
|
+
]
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
if (backgroundImage) {
|
|
1592
|
+
const bgStyle = {
|
|
1593
|
+
backgroundImage: `url("${backgroundImage}")`,
|
|
1594
|
+
backgroundSize: "cover",
|
|
1595
|
+
backgroundPosition: "center"
|
|
1596
|
+
};
|
|
1597
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1598
|
+
"section",
|
|
1501
1599
|
{
|
|
1502
|
-
className:
|
|
1600
|
+
className: "relative isolate overflow-hidden",
|
|
1601
|
+
style: bgStyle,
|
|
1602
|
+
"data-testid": testId,
|
|
1603
|
+
"data-bg": testId && `${testId}-bg`,
|
|
1503
1604
|
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 }) })
|
|
1605
|
+
overlay && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
|
|
1606
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative mx-auto max-w-7xl px-5 py-28 lg:px-8 lg:py-36", children: copy })
|
|
1509
1607
|
]
|
|
1510
1608
|
}
|
|
1511
|
-
)
|
|
1512
|
-
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1513
1611
|
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
1612
|
copy,
|
|
1515
1613
|
imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1594,23 +1692,49 @@ var TONE_SURFACE2 = {
|
|
|
1594
1692
|
muted: "bg-muted text-foreground",
|
|
1595
1693
|
ink: "bg-foreground text-background"
|
|
1596
1694
|
};
|
|
1597
|
-
function CTABanner({
|
|
1598
|
-
|
|
1695
|
+
function CTABanner({
|
|
1696
|
+
title,
|
|
1697
|
+
subtitle,
|
|
1698
|
+
cta,
|
|
1699
|
+
tone = "default",
|
|
1700
|
+
backgroundImage,
|
|
1701
|
+
overlay = true,
|
|
1702
|
+
testId
|
|
1703
|
+
}) {
|
|
1704
|
+
const onImage = Boolean(backgroundImage);
|
|
1705
|
+
const subtle = onImage ? "text-white/80" : tone === "default" ? "text-primary-foreground/80" : tone === "ink" ? "text-background/70" : "text-muted-foreground";
|
|
1706
|
+
const bgStyle = backgroundImage ? {
|
|
1707
|
+
backgroundImage: `url("${backgroundImage}")`,
|
|
1708
|
+
backgroundSize: "cover",
|
|
1709
|
+
backgroundPosition: "center"
|
|
1710
|
+
} : void 0;
|
|
1599
1711
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1600
1712
|
"div",
|
|
1601
1713
|
{
|
|
1602
1714
|
className: react.cn(
|
|
1603
|
-
"rounded-2xl px-8 py-14 text-center",
|
|
1604
|
-
TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
|
|
1715
|
+
"relative isolate overflow-hidden rounded-2xl px-8 py-14 text-center",
|
|
1716
|
+
onImage ? "text-white" : TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
|
|
1605
1717
|
),
|
|
1718
|
+
style: bgStyle,
|
|
1606
1719
|
"data-testid": testId,
|
|
1607
1720
|
children: [
|
|
1608
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1613
|
-
|
|
1721
|
+
onImage && overlay && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
|
|
1722
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
1723
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
|
|
1724
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: react.cn("mx-auto mt-4 max-w-2xl text-lg", subtle), children: subtitle }),
|
|
1725
|
+
cta?.label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1726
|
+
react.Button,
|
|
1727
|
+
{
|
|
1728
|
+
asChild: true,
|
|
1729
|
+
size: "lg",
|
|
1730
|
+
variant: tone === "default" && !onImage ? "secondary" : "default",
|
|
1731
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: cta.href || "#", children: [
|
|
1732
|
+
cta.label,
|
|
1733
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
1734
|
+
] })
|
|
1735
|
+
}
|
|
1736
|
+
) })
|
|
1737
|
+
] })
|
|
1614
1738
|
]
|
|
1615
1739
|
}
|
|
1616
1740
|
);
|
|
@@ -1775,6 +1899,449 @@ function Container({ width = "default", padding = "md", children, testId }) {
|
|
|
1775
1899
|
}
|
|
1776
1900
|
);
|
|
1777
1901
|
}
|
|
1902
|
+
function PricingTable({ plans, testId }) {
|
|
1903
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1904
|
+
"div",
|
|
1905
|
+
{
|
|
1906
|
+
className: react.cn("grid gap-6", (plans?.length ?? 0) >= 3 ? "md:grid-cols-3" : "sm:grid-cols-2"),
|
|
1907
|
+
"data-testid": testId,
|
|
1908
|
+
children: (plans ?? []).map((plan, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1909
|
+
react.Card,
|
|
1910
|
+
{
|
|
1911
|
+
variant: plan.featured ? "elevated" : "outline",
|
|
1912
|
+
className: react.cn("flex h-full flex-col p-6", plan.featured && "ring-2 ring-primary"),
|
|
1913
|
+
"data-testid": testId && `${testId}-item-${i}`,
|
|
1914
|
+
children: [
|
|
1915
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold tracking-tight", children: plan.name }),
|
|
1916
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-baseline gap-1", children: [
|
|
1917
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl font-semibold tracking-tight", children: plan.price }),
|
|
1918
|
+
plan.period && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
1919
|
+
"/",
|
|
1920
|
+
plan.period
|
|
1921
|
+
] })
|
|
1922
|
+
] }),
|
|
1923
|
+
plan.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: plan.description }),
|
|
1924
|
+
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: [
|
|
1925
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "mt-0.5 size-4 shrink-0 text-primary" }),
|
|
1926
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: f })
|
|
1927
|
+
] }, j)) }),
|
|
1928
|
+
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 }) })
|
|
1929
|
+
]
|
|
1930
|
+
},
|
|
1931
|
+
i
|
|
1932
|
+
))
|
|
1933
|
+
}
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
function Tabs({ items, testId }) {
|
|
1937
|
+
const [active, setActive] = react$1.useState(0);
|
|
1938
|
+
const list = items ?? [];
|
|
1939
|
+
const current = list[Math.min(active, Math.max(list.length - 1, 0))];
|
|
1940
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
|
|
1941
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 border-b border-border", role: "tablist", children: list.map((t, i) => {
|
|
1942
|
+
const selected = i === active;
|
|
1943
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1944
|
+
"button",
|
|
1945
|
+
{
|
|
1946
|
+
type: "button",
|
|
1947
|
+
role: "tab",
|
|
1948
|
+
"aria-selected": selected,
|
|
1949
|
+
onClick: () => setActive(i),
|
|
1950
|
+
className: react.cn(
|
|
1951
|
+
"-mb-px border-b-2 px-4 py-2 text-sm font-medium transition-colors",
|
|
1952
|
+
selected ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
|
|
1953
|
+
),
|
|
1954
|
+
"data-testid": testId && `${testId}-tab-${i}`,
|
|
1955
|
+
children: t.label
|
|
1956
|
+
},
|
|
1957
|
+
i
|
|
1958
|
+
);
|
|
1959
|
+
}) }),
|
|
1960
|
+
current && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1961
|
+
"div",
|
|
1962
|
+
{
|
|
1963
|
+
role: "tabpanel",
|
|
1964
|
+
className: "pt-5 text-sm leading-relaxed text-muted-foreground",
|
|
1965
|
+
"data-testid": testId && `${testId}-panel`,
|
|
1966
|
+
children: current.content
|
|
1967
|
+
}
|
|
1968
|
+
)
|
|
1969
|
+
] });
|
|
1970
|
+
}
|
|
1971
|
+
function Accordion({ items, allowMultiple = false, testId }) {
|
|
1972
|
+
const [open, setOpen] = react$1.useState([]);
|
|
1973
|
+
const toggle2 = (i) => setOpen(
|
|
1974
|
+
(prev) => prev.includes(i) ? prev.filter((n) => n !== i) : allowMultiple ? [...prev, i] : [i]
|
|
1975
|
+
);
|
|
1976
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border border-y border-border", "data-testid": testId, children: (items ?? []).map((item, i) => {
|
|
1977
|
+
const isOpen = open.includes(i);
|
|
1978
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
|
|
1979
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1980
|
+
"button",
|
|
1981
|
+
{
|
|
1982
|
+
type: "button",
|
|
1983
|
+
"aria-expanded": isOpen,
|
|
1984
|
+
onClick: () => toggle2(i),
|
|
1985
|
+
className: "flex w-full items-center justify-between gap-4 py-4 text-left text-base font-medium",
|
|
1986
|
+
"data-testid": testId && `${testId}-trigger-${i}`,
|
|
1987
|
+
children: [
|
|
1988
|
+
item.title,
|
|
1989
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1990
|
+
lucideReact.ChevronDown,
|
|
1991
|
+
{
|
|
1992
|
+
className: react.cn("size-4 shrink-0 transition-transform", isOpen && "rotate-180")
|
|
1993
|
+
}
|
|
1994
|
+
)
|
|
1995
|
+
]
|
|
1996
|
+
}
|
|
1997
|
+
),
|
|
1998
|
+
isOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1999
|
+
"p",
|
|
2000
|
+
{
|
|
2001
|
+
className: "pb-4 text-sm leading-relaxed text-muted-foreground",
|
|
2002
|
+
"data-testid": testId && `${testId}-content-${i}`,
|
|
2003
|
+
children: item.content
|
|
2004
|
+
}
|
|
2005
|
+
)
|
|
2006
|
+
] }, i);
|
|
2007
|
+
}) });
|
|
2008
|
+
}
|
|
2009
|
+
var COLS2 = {
|
|
2010
|
+
"2": "grid-cols-2",
|
|
2011
|
+
"3": "grid-cols-2 sm:grid-cols-3",
|
|
2012
|
+
"4": "grid-cols-2 sm:grid-cols-4"
|
|
2013
|
+
};
|
|
2014
|
+
function Gallery({
|
|
2015
|
+
images,
|
|
2016
|
+
columns = "3",
|
|
2017
|
+
aspect = "1:1",
|
|
2018
|
+
rounded = "lg",
|
|
2019
|
+
testId
|
|
2020
|
+
}) {
|
|
2021
|
+
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: [
|
|
2022
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2023
|
+
BlockImage,
|
|
2024
|
+
{
|
|
2025
|
+
src: img.src,
|
|
2026
|
+
alt: img.alt,
|
|
2027
|
+
aspect,
|
|
2028
|
+
rounded,
|
|
2029
|
+
testId: testId && `${testId}-item-${i}-image`
|
|
2030
|
+
}
|
|
2031
|
+
),
|
|
2032
|
+
img.caption && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: img.caption })
|
|
2033
|
+
] }, i)) });
|
|
2034
|
+
}
|
|
2035
|
+
var ASPECT2 = {
|
|
2036
|
+
"16:9": "aspect-video",
|
|
2037
|
+
"4:3": "aspect-[4/3]",
|
|
2038
|
+
"1:1": "aspect-square"
|
|
2039
|
+
};
|
|
2040
|
+
function toEmbedUrl(url) {
|
|
2041
|
+
if (!url) return null;
|
|
2042
|
+
const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]{11})/);
|
|
2043
|
+
if (yt) return `https://www.youtube.com/embed/${yt[1]}`;
|
|
2044
|
+
const vimeo = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
|
|
2045
|
+
if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;
|
|
2046
|
+
const loom = url.match(/loom\.com\/(?:share|embed)\/([\w-]+)/);
|
|
2047
|
+
if (loom) return `https://www.loom.com/embed/${loom[1]}`;
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
function VideoEmbed({
|
|
2051
|
+
url,
|
|
2052
|
+
title,
|
|
2053
|
+
aspect = "16:9",
|
|
2054
|
+
rounded = true,
|
|
2055
|
+
testId
|
|
2056
|
+
}) {
|
|
2057
|
+
const embed = toEmbedUrl(url);
|
|
2058
|
+
const frame = react.cn(
|
|
2059
|
+
"w-full overflow-hidden border border-border bg-muted",
|
|
2060
|
+
ASPECT2[aspect] ?? ASPECT2["16:9"],
|
|
2061
|
+
rounded && "rounded-lg"
|
|
2062
|
+
);
|
|
2063
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frame, "data-testid": testId, children: embed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2064
|
+
"iframe",
|
|
2065
|
+
{
|
|
2066
|
+
src: embed,
|
|
2067
|
+
title: title || "Embedded video",
|
|
2068
|
+
className: "size-full",
|
|
2069
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
2070
|
+
allowFullScreen: true,
|
|
2071
|
+
"data-testid": testId && `${testId}-iframe`
|
|
2072
|
+
}
|
|
2073
|
+
) : url ? /* @__PURE__ */ jsxRuntime.jsx("video", { src: url, controls: true, className: "size-full", "data-testid": testId && `${testId}-video` }) : null });
|
|
2074
|
+
}
|
|
2075
|
+
function ContactForm({
|
|
2076
|
+
fields,
|
|
2077
|
+
submitLabel = "Send",
|
|
2078
|
+
action,
|
|
2079
|
+
successMessage = "Thanks \u2014 we\u2019ll be in touch shortly.",
|
|
2080
|
+
testId
|
|
2081
|
+
}) {
|
|
2082
|
+
const [sent, setSent] = react$1.useState(false);
|
|
2083
|
+
const list = fields ?? [];
|
|
2084
|
+
const onSubmit = (e) => {
|
|
2085
|
+
if (!action) {
|
|
2086
|
+
e.preventDefault();
|
|
2087
|
+
setSent(true);
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
if (sent) {
|
|
2091
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2092
|
+
"p",
|
|
2093
|
+
{
|
|
2094
|
+
className: "rounded-lg border border-border bg-muted p-6 text-center text-sm text-foreground",
|
|
2095
|
+
role: "status",
|
|
2096
|
+
"data-testid": testId && `${testId}-success`,
|
|
2097
|
+
children: successMessage
|
|
2098
|
+
}
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2102
|
+
"form",
|
|
2103
|
+
{
|
|
2104
|
+
action,
|
|
2105
|
+
method: action ? "post" : void 0,
|
|
2106
|
+
onSubmit,
|
|
2107
|
+
className: "mx-auto flex max-w-xl flex-col gap-4",
|
|
2108
|
+
"data-testid": testId,
|
|
2109
|
+
children: [
|
|
2110
|
+
list.map((f, i) => {
|
|
2111
|
+
const inputClass = "w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground outline-none focus:border-primary";
|
|
2112
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-1.5 text-left", children: [
|
|
2113
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: f.label }),
|
|
2114
|
+
f.type === "textarea" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2115
|
+
"textarea",
|
|
2116
|
+
{
|
|
2117
|
+
name: f.name,
|
|
2118
|
+
required: f.required,
|
|
2119
|
+
rows: 4,
|
|
2120
|
+
className: inputClass,
|
|
2121
|
+
"data-testid": testId && `${testId}-field-${f.name}`
|
|
2122
|
+
}
|
|
2123
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2124
|
+
"input",
|
|
2125
|
+
{
|
|
2126
|
+
name: f.name,
|
|
2127
|
+
type: f.type || "text",
|
|
2128
|
+
required: f.required,
|
|
2129
|
+
className: inputClass,
|
|
2130
|
+
"data-testid": testId && `${testId}-field-${f.name}`
|
|
2131
|
+
}
|
|
2132
|
+
)
|
|
2133
|
+
] }, i);
|
|
2134
|
+
}),
|
|
2135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2136
|
+
react.Button,
|
|
2137
|
+
{
|
|
2138
|
+
type: "submit",
|
|
2139
|
+
className: react.cn("mt-1 self-start"),
|
|
2140
|
+
"data-testid": testId && `${testId}-submit`,
|
|
2141
|
+
children: submitLabel
|
|
2142
|
+
}
|
|
2143
|
+
)
|
|
2144
|
+
]
|
|
2145
|
+
}
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
var TONE = {
|
|
2149
|
+
primary: "bg-primary text-primary-foreground",
|
|
2150
|
+
muted: "bg-muted text-foreground",
|
|
2151
|
+
ink: "bg-foreground text-background"
|
|
2152
|
+
};
|
|
2153
|
+
function Banner({ text, cta, tone = "primary", testId }) {
|
|
2154
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2155
|
+
"div",
|
|
2156
|
+
{
|
|
2157
|
+
className: react.cn("w-full px-5 py-2.5 text-center text-sm", TONE[tone] ?? TONE.primary),
|
|
2158
|
+
"data-testid": testId,
|
|
2159
|
+
children: [
|
|
2160
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: text }),
|
|
2161
|
+
cta?.label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2162
|
+
"a",
|
|
2163
|
+
{
|
|
2164
|
+
href: cta.href || "#",
|
|
2165
|
+
className: "ml-2 inline-flex items-center gap-1 font-semibold underline underline-offset-4",
|
|
2166
|
+
"data-testid": testId && `${testId}-cta`,
|
|
2167
|
+
children: [
|
|
2168
|
+
cta.label,
|
|
2169
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-3.5" })
|
|
2170
|
+
]
|
|
2171
|
+
}
|
|
2172
|
+
)
|
|
2173
|
+
]
|
|
2174
|
+
}
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
function MediaText({
|
|
2178
|
+
eyebrow,
|
|
2179
|
+
title,
|
|
2180
|
+
body,
|
|
2181
|
+
imageUrl,
|
|
2182
|
+
mediaSide = "left",
|
|
2183
|
+
cta,
|
|
2184
|
+
testId
|
|
2185
|
+
}) {
|
|
2186
|
+
const media = /* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: imageUrl, aspect: "4:3", rounded: "lg", testId: testId && `${testId}-image` });
|
|
2187
|
+
const copy = /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2188
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
|
|
2189
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance", children: title }),
|
|
2190
|
+
body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-4 text-lg leading-relaxed text-muted-foreground", children: body }),
|
|
2191
|
+
cta?.label && /* @__PURE__ */ jsxRuntime.jsx(react.Button, { asChild: true, className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: cta.href || "#", children: [
|
|
2192
|
+
cta.label,
|
|
2193
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
|
|
2194
|
+
] }) })
|
|
2195
|
+
] });
|
|
2196
|
+
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: [
|
|
2197
|
+
copy,
|
|
2198
|
+
media
|
|
2199
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2200
|
+
media,
|
|
2201
|
+
copy
|
|
2202
|
+
] }) });
|
|
2203
|
+
}
|
|
2204
|
+
var COLS3 = {
|
|
2205
|
+
"2": "sm:grid-cols-2",
|
|
2206
|
+
"3": "sm:grid-cols-2 lg:grid-cols-3",
|
|
2207
|
+
"4": "sm:grid-cols-2 lg:grid-cols-4"
|
|
2208
|
+
};
|
|
2209
|
+
function TeamGrid({ members, columns = "3", testId }) {
|
|
2210
|
+
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(
|
|
2211
|
+
react.Card,
|
|
2212
|
+
{
|
|
2213
|
+
variant: "outline",
|
|
2214
|
+
className: "flex h-full flex-col items-center p-6 text-center",
|
|
2215
|
+
"data-testid": testId && `${testId}-item-${i}`,
|
|
2216
|
+
children: [
|
|
2217
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Avatar, { className: "size-20", children: [
|
|
2218
|
+
m.avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(react.AvatarImage, { src: m.avatarUrl, alt: m.name }),
|
|
2219
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.AvatarFallback, { children: initials(m.name || "?") })
|
|
2220
|
+
] }),
|
|
2221
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: m.name }),
|
|
2222
|
+
m.role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-primary", children: m.role }),
|
|
2223
|
+
m.bio && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: m.bio })
|
|
2224
|
+
]
|
|
2225
|
+
},
|
|
2226
|
+
i
|
|
2227
|
+
)) });
|
|
2228
|
+
}
|
|
2229
|
+
function Steps({ steps, variant = "numbered", testId }) {
|
|
2230
|
+
const list = steps ?? [];
|
|
2231
|
+
if (variant === "timeline") {
|
|
2232
|
+
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: [
|
|
2233
|
+
/* @__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 }),
|
|
2234
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold tracking-tight", children: s.title }),
|
|
2235
|
+
s.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: s.description })
|
|
2236
|
+
] }, i)) });
|
|
2237
|
+
}
|
|
2238
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2239
|
+
"div",
|
|
2240
|
+
{
|
|
2241
|
+
className: react.cn("grid gap-6", list.length >= 4 ? "md:grid-cols-4" : "sm:grid-cols-3"),
|
|
2242
|
+
"data-testid": testId,
|
|
2243
|
+
children: list.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
|
|
2244
|
+
/* @__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 }),
|
|
2245
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: s.title }),
|
|
2246
|
+
s.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: s.description })
|
|
2247
|
+
] }, i))
|
|
2248
|
+
}
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
var COLS4 = {
|
|
2252
|
+
"2": "sm:grid-cols-2",
|
|
2253
|
+
"3": "sm:grid-cols-2 lg:grid-cols-3"
|
|
2254
|
+
};
|
|
2255
|
+
function TestimonialGrid({ items, columns = "3", testId }) {
|
|
2256
|
+
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(
|
|
2257
|
+
react.Card,
|
|
2258
|
+
{
|
|
2259
|
+
variant: "outline",
|
|
2260
|
+
className: "flex h-full flex-col p-6",
|
|
2261
|
+
"data-testid": testId && `${testId}-item-${i}`,
|
|
2262
|
+
children: [
|
|
2263
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Quote, { className: "size-6 text-primary/40", "aria-hidden": true }),
|
|
2264
|
+
/* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: "mt-3 flex-1 text-base leading-relaxed", children: t.quote }),
|
|
2265
|
+
(t.author || t.avatarUrl) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-center gap-3", children: [
|
|
2266
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Avatar, { className: "size-9", children: [
|
|
2267
|
+
t.avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(react.AvatarImage, { src: t.avatarUrl, alt: t.author ?? "" }),
|
|
2268
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.AvatarFallback, { children: initials(t.author || "?") })
|
|
2269
|
+
] }),
|
|
2270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2271
|
+
t.author && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: t.author }),
|
|
2272
|
+
t.role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t.role })
|
|
2273
|
+
] })
|
|
2274
|
+
] })
|
|
2275
|
+
]
|
|
2276
|
+
},
|
|
2277
|
+
i
|
|
2278
|
+
)) });
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// src/studio/publish.ts
|
|
2282
|
+
function blobToDataUrl(blob) {
|
|
2283
|
+
return new Promise((resolve, reject) => {
|
|
2284
|
+
const reader = new FileReader();
|
|
2285
|
+
reader.onload = () => resolve(reader.result);
|
|
2286
|
+
reader.onerror = () => reject(reader.error);
|
|
2287
|
+
reader.readAsDataURL(blob);
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
function createArbiMaterializer(arbi, opts = {}) {
|
|
2291
|
+
return async (docId) => {
|
|
2292
|
+
try {
|
|
2293
|
+
const res = await arbi.documents.download(docId);
|
|
2294
|
+
const blob = await res.blob();
|
|
2295
|
+
if (opts.publishAsset) return await opts.publishAsset(docId, blob);
|
|
2296
|
+
return await blobToDataUrl(blob);
|
|
2297
|
+
} catch {
|
|
2298
|
+
return null;
|
|
2299
|
+
}
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
function collectAssetRefs(node, acc) {
|
|
2303
|
+
if (typeof node === "string") {
|
|
2304
|
+
if (isAssetRef(node)) acc.add(node);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
if (Array.isArray(node)) {
|
|
2308
|
+
for (const child of node) collectAssetRefs(child, acc);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
if (node && typeof node === "object") {
|
|
2312
|
+
for (const value of Object.values(node)) collectAssetRefs(value, acc);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
function replaceAssetRefs(node, replacements) {
|
|
2316
|
+
if (typeof node === "string") {
|
|
2317
|
+
return replacements.get(node) ?? node;
|
|
2318
|
+
}
|
|
2319
|
+
if (Array.isArray(node)) {
|
|
2320
|
+
return node.map((child) => replaceAssetRefs(child, replacements));
|
|
2321
|
+
}
|
|
2322
|
+
if (node && typeof node === "object") {
|
|
2323
|
+
const out = {};
|
|
2324
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2325
|
+
out[key] = replaceAssetRefs(value, replacements);
|
|
2326
|
+
}
|
|
2327
|
+
return out;
|
|
2328
|
+
}
|
|
2329
|
+
return node;
|
|
2330
|
+
}
|
|
2331
|
+
async function materializePageData(data, materialize) {
|
|
2332
|
+
const refs = /* @__PURE__ */ new Set();
|
|
2333
|
+
collectAssetRefs(data, refs);
|
|
2334
|
+
if (refs.size === 0) return data;
|
|
2335
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
2336
|
+
await Promise.all(
|
|
2337
|
+
[...refs].map(async (ref) => {
|
|
2338
|
+
const src = await materialize(assetRefId(ref));
|
|
2339
|
+
if (src) replacements.set(ref, src);
|
|
2340
|
+
})
|
|
2341
|
+
);
|
|
2342
|
+
if (replacements.size === 0) return data;
|
|
2343
|
+
return replaceAssetRefs(data, replacements);
|
|
2344
|
+
}
|
|
1778
2345
|
var EMPTY_DATA = { content: [], root: {} };
|
|
1779
2346
|
function StudioEditor({
|
|
1780
2347
|
config,
|
|
@@ -1787,9 +2354,14 @@ function StudioEditor({
|
|
|
1787
2354
|
backLabel = "Back",
|
|
1788
2355
|
live,
|
|
1789
2356
|
initialSlug,
|
|
1790
|
-
testIdPrefix = "studio"
|
|
2357
|
+
testIdPrefix = "studio",
|
|
2358
|
+
publishAsset
|
|
1791
2359
|
}) {
|
|
1792
2360
|
const arbi = react.useArbi();
|
|
2361
|
+
const materialize = react$1.useMemo(
|
|
2362
|
+
() => createArbiMaterializer(arbi, { publishAsset }),
|
|
2363
|
+
[arbi, publishAsset]
|
|
2364
|
+
);
|
|
1793
2365
|
const [slug, setSlug] = react$1.useState(initialSlug ?? pages[0]?.slug ?? "home");
|
|
1794
2366
|
const [data, setData] = react$1.useState(EMPTY_DATA);
|
|
1795
2367
|
const [rev, setRev] = react$1.useState(0);
|
|
@@ -1817,11 +2389,16 @@ function StudioEditor({
|
|
|
1817
2389
|
const persist4 = react$1.useCallback(
|
|
1818
2390
|
async (toSave) => {
|
|
1819
2391
|
setSaving(true);
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2392
|
+
try {
|
|
2393
|
+
const where = await persistence.savePage(arbi, live, slug, toSave);
|
|
2394
|
+
const published = await materializePageData(toSave, materialize);
|
|
2395
|
+
await persistence.savePublished(arbi, live, slug, published);
|
|
2396
|
+
setVia(where);
|
|
2397
|
+
} finally {
|
|
2398
|
+
setSaving(false);
|
|
2399
|
+
}
|
|
1823
2400
|
},
|
|
1824
|
-
[arbi, live, slug, persistence]
|
|
2401
|
+
[arbi, live, slug, persistence, materialize]
|
|
1825
2402
|
);
|
|
1826
2403
|
react$1.useEffect(() => {
|
|
1827
2404
|
let cancelled = false;
|
|
@@ -1851,7 +2428,7 @@ function StudioEditor({
|
|
|
1851
2428
|
reactRouterDom.Link,
|
|
1852
2429
|
{
|
|
1853
2430
|
to: backHref,
|
|
1854
|
-
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:text-
|
|
2431
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:text-primary",
|
|
1855
2432
|
"data-testid": `${testIdPrefix}-back`,
|
|
1856
2433
|
children: [
|
|
1857
2434
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "size-4" }),
|
|
@@ -1862,7 +2439,7 @@ function StudioEditor({
|
|
|
1862
2439
|
),
|
|
1863
2440
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-4 w-px bg-border" })
|
|
1864
2441
|
] }),
|
|
1865
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-display text-sm font-semibold text-
|
|
2442
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-display text-sm font-semibold text-foreground", children: brandLabel }),
|
|
1866
2443
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1867
2444
|
"select",
|
|
1868
2445
|
{
|
|
@@ -1890,7 +2467,7 @@ function StudioEditor({
|
|
|
1890
2467
|
{
|
|
1891
2468
|
type: "button",
|
|
1892
2469
|
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-
|
|
2470
|
+
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
2471
|
"data-testid": `${testIdPrefix}-theme-toggle`,
|
|
1895
2472
|
children: [
|
|
1896
2473
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { className: "size-4" }),
|
|
@@ -1904,7 +2481,7 @@ function StudioEditor({
|
|
|
1904
2481
|
type: "button",
|
|
1905
2482
|
onClick: () => void persist4(latest.current),
|
|
1906
2483
|
disabled: saving || loading,
|
|
1907
|
-
className: "inline-flex items-center gap-1.5 rounded-md bg-
|
|
2484
|
+
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
2485
|
"data-testid": `${testIdPrefix}-publish`,
|
|
1909
2486
|
children: [
|
|
1910
2487
|
saving ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rocket, { className: "size-4" }),
|
|
@@ -1927,7 +2504,7 @@ function StudioEditor({
|
|
|
1927
2504
|
"\u201D\u2026"
|
|
1928
2505
|
]
|
|
1929
2506
|
}
|
|
1930
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2507
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(AssetResolverProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1931
2508
|
puck.Puck,
|
|
1932
2509
|
{
|
|
1933
2510
|
config,
|
|
@@ -1938,7 +2515,7 @@ function StudioEditor({
|
|
|
1938
2515
|
onPublish: (d) => void persist4(d)
|
|
1939
2516
|
},
|
|
1940
2517
|
`${slug}-${rev}`
|
|
1941
|
-
) }),
|
|
2518
|
+
) }) }),
|
|
1942
2519
|
themeOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1943
2520
|
"aside",
|
|
1944
2521
|
{
|
|
@@ -1976,11 +2553,13 @@ function PageRenderer({
|
|
|
1976
2553
|
let cancelled = false;
|
|
1977
2554
|
setLoading(true);
|
|
1978
2555
|
void (async () => {
|
|
1979
|
-
const [{ data:
|
|
1980
|
-
persistence.
|
|
2556
|
+
const [{ data: published }, { data: theme }] = await Promise.all([
|
|
2557
|
+
persistence.loadPublished(arbi, live, slug),
|
|
1981
2558
|
persistence.loadTheme(arbi, live)
|
|
1982
2559
|
]);
|
|
1983
2560
|
if (cancelled) return;
|
|
2561
|
+
const page = published ?? (await persistence.loadPage(arbi, live, slug)).data;
|
|
2562
|
+
if (cancelled) return;
|
|
1984
2563
|
applyThemeVars(
|
|
1985
2564
|
theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
|
|
1986
2565
|
);
|
|
@@ -2010,7 +2589,7 @@ function PageRenderer({
|
|
|
2010
2589
|
className: "mx-auto max-w-2xl px-5 py-24 text-center",
|
|
2011
2590
|
"data-testid": `${testIdPrefix}-render-empty`,
|
|
2012
2591
|
children: [
|
|
2013
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-display text-2xl font-semibold text-
|
|
2592
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-display text-2xl font-semibold text-foreground", children: title }),
|
|
2014
2593
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: body })
|
|
2015
2594
|
]
|
|
2016
2595
|
}
|
|
@@ -2024,9 +2603,11 @@ function createStudioPersistence(cfg) {
|
|
|
2024
2603
|
const WORKSPACE_ID = cfg.workspaceId;
|
|
2025
2604
|
const FOLDER = cfg.folder ?? "site";
|
|
2026
2605
|
const pageKey = (slug) => `${cfg.storagePrefix}${slug}`;
|
|
2606
|
+
const publishedKey = (slug) => `${cfg.storagePrefix}published-${slug}`;
|
|
2027
2607
|
const THEME_KEY = cfg.themeStorageKey ?? `${cfg.storagePrefix}theme`;
|
|
2028
2608
|
const THEME_FILE = cfg.themeFileName ?? "theme.json";
|
|
2029
2609
|
const pageFile = cfg.pageFileName ?? ((slug) => `${slug}.json`);
|
|
2610
|
+
const publishedFile = cfg.publishedFileName ?? ((slug) => `${slug}.published.json`);
|
|
2030
2611
|
function canUseArbi(arbi, live) {
|
|
2031
2612
|
return live && !!arbi;
|
|
2032
2613
|
}
|
|
@@ -2049,39 +2630,47 @@ function createStudioPersistence(cfg) {
|
|
|
2049
2630
|
return JSON.parse(raw);
|
|
2050
2631
|
}
|
|
2051
2632
|
}
|
|
2052
|
-
async function
|
|
2633
|
+
async function writePage(arbi, live, kind, localKey, fileName, data) {
|
|
2053
2634
|
try {
|
|
2054
|
-
localStorage.setItem(
|
|
2635
|
+
localStorage.setItem(localKey, JSON.stringify(data));
|
|
2055
2636
|
} catch {
|
|
2056
2637
|
}
|
|
2057
2638
|
if (canUseArbi(arbi, live)) {
|
|
2058
2639
|
try {
|
|
2059
|
-
await uploadJson(arbi,
|
|
2060
|
-
console.info(`[studio] saved
|
|
2640
|
+
await uploadJson(arbi, fileName, data);
|
|
2641
|
+
console.info(`[studio] saved ${kind} "${fileName}" \u2192 ARBI (${WORKSPACE_ID})`);
|
|
2061
2642
|
return "arbi";
|
|
2062
2643
|
} catch (e) {
|
|
2063
|
-
console.warn(`[studio] ARBI save failed for "${
|
|
2644
|
+
console.warn(`[studio] ARBI save failed for "${fileName}", kept localStorage`, e);
|
|
2064
2645
|
}
|
|
2065
2646
|
}
|
|
2066
|
-
console.info(`[studio] saved
|
|
2647
|
+
console.info(`[studio] saved ${kind} "${fileName}" \u2192 localStorage`);
|
|
2067
2648
|
return "local";
|
|
2068
2649
|
}
|
|
2069
|
-
async function
|
|
2650
|
+
async function readPage(arbi, live, localKey, fileName) {
|
|
2070
2651
|
if (canUseArbi(arbi, live)) {
|
|
2071
2652
|
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
|
-
}
|
|
2653
|
+
const data = await downloadJson(arbi, fileName);
|
|
2654
|
+
if (data) return { data, via: "arbi" };
|
|
2077
2655
|
} catch (e) {
|
|
2078
|
-
console.warn(`[studio] ARBI load failed for "${
|
|
2656
|
+
console.warn(`[studio] ARBI load failed for "${fileName}", trying localStorage`, e);
|
|
2079
2657
|
}
|
|
2080
2658
|
}
|
|
2081
|
-
const raw = safeLocalGet(
|
|
2082
|
-
console.info(`[studio] loaded page "${slug}" \u2190 localStorage`);
|
|
2659
|
+
const raw = safeLocalGet(localKey);
|
|
2083
2660
|
return { data: raw ? JSON.parse(raw) : null, via: "local" };
|
|
2084
2661
|
}
|
|
2662
|
+
function savePage(arbi, live, slug, data) {
|
|
2663
|
+
return writePage(arbi, live, "page", pageKey(slug), pageFile(slug), data);
|
|
2664
|
+
}
|
|
2665
|
+
function loadPage(arbi, live, slug) {
|
|
2666
|
+
return readPage(arbi, live, pageKey(slug), pageFile(slug));
|
|
2667
|
+
}
|
|
2668
|
+
function savePublished(arbi, live, slug, data) {
|
|
2669
|
+
return writePage(arbi, live, "published page", publishedKey(slug), publishedFile(slug), data);
|
|
2670
|
+
}
|
|
2671
|
+
function loadPublished(arbi, live, slug) {
|
|
2672
|
+
return readPage(arbi, live, publishedKey(slug), publishedFile(slug));
|
|
2673
|
+
}
|
|
2085
2674
|
async function saveTheme(arbi, live, theme) {
|
|
2086
2675
|
try {
|
|
2087
2676
|
localStorage.setItem(THEME_KEY, JSON.stringify(theme));
|
|
@@ -2118,7 +2707,7 @@ function createStudioPersistence(cfg) {
|
|
|
2118
2707
|
return null;
|
|
2119
2708
|
}
|
|
2120
2709
|
}
|
|
2121
|
-
return { savePage, loadPage, saveTheme, loadTheme };
|
|
2710
|
+
return { savePage, loadPage, savePublished, loadPublished, saveTheme, loadTheme };
|
|
2122
2711
|
}
|
|
2123
2712
|
function AiTextField({
|
|
2124
2713
|
value,
|
|
@@ -2233,21 +2822,293 @@ function createAiFields(cfg) {
|
|
|
2233
2822
|
});
|
|
2234
2823
|
return { aiText, aiTextarea };
|
|
2235
2824
|
}
|
|
2236
|
-
function
|
|
2237
|
-
|
|
2238
|
-
|
|
2825
|
+
function latestImageDocId(artifacts) {
|
|
2826
|
+
for (let i = artifacts.length - 1; i >= 0; i--) {
|
|
2827
|
+
if (artifacts[i].mode === "image") return artifacts[i].doc_ext_id;
|
|
2828
|
+
}
|
|
2829
|
+
return null;
|
|
2239
2830
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2831
|
+
function useImageGen() {
|
|
2832
|
+
const task = useFirmAiTask();
|
|
2833
|
+
const { run } = task;
|
|
2834
|
+
const generate = react$1.useCallback(
|
|
2835
|
+
async (prompt) => {
|
|
2836
|
+
const trimmed = prompt.trim();
|
|
2837
|
+
if (!trimmed) return null;
|
|
2838
|
+
const result = await run(`/image ${trimmed}`, []);
|
|
2839
|
+
return latestImageDocId(result?.artifacts ?? []);
|
|
2840
|
+
},
|
|
2841
|
+
[run]
|
|
2842
|
+
);
|
|
2843
|
+
const docExtId = react$1.useMemo(() => latestImageDocId(task.artifacts), [task.artifacts]);
|
|
2844
|
+
return {
|
|
2845
|
+
docExtId,
|
|
2846
|
+
status: task.status,
|
|
2847
|
+
error: task.error,
|
|
2848
|
+
isGenerating: task.isRunning,
|
|
2849
|
+
generate,
|
|
2850
|
+
reset: task.reset
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
var IMAGE_EXT = /\.(png|jpe?g|webp|gif|avif|svg)$/i;
|
|
2854
|
+
function ImageFieldControl({
|
|
2855
|
+
value,
|
|
2856
|
+
onChange,
|
|
2857
|
+
name,
|
|
2858
|
+
workspaceId,
|
|
2859
|
+
folder,
|
|
2860
|
+
testIdPrefix
|
|
2861
|
+
}) {
|
|
2862
|
+
const [mode, setMode] = react$1.useState("asset");
|
|
2863
|
+
const [url, setUrl] = react$1.useState("");
|
|
2864
|
+
const [prompt, setPrompt] = react$1.useState("");
|
|
2865
|
+
const [busy, setBusy] = react$1.useState(false);
|
|
2866
|
+
const arbi = react.useArbi();
|
|
2867
|
+
const gen = useImageGen();
|
|
2868
|
+
const tp = (...q) => tid(`${testIdPrefix}-image`, name, ...q);
|
|
2869
|
+
const { data: docs } = react.useDocuments(workspaceId);
|
|
2870
|
+
const imageDocs = react$1.useMemo(
|
|
2871
|
+
() => (docs ?? []).filter(
|
|
2872
|
+
(d) => d.file_name && IMAGE_EXT.test(d.file_name)
|
|
2873
|
+
),
|
|
2874
|
+
[docs]
|
|
2875
|
+
);
|
|
2876
|
+
const imageIds = react$1.useMemo(
|
|
2877
|
+
() => imageDocs.map((d) => d.external_id),
|
|
2878
|
+
[imageDocs]
|
|
2879
|
+
);
|
|
2880
|
+
const { data: thumbs } = react.useThumbnails(imageIds);
|
|
2881
|
+
const onUpload = async (e) => {
|
|
2882
|
+
const file = e.target.files?.[0];
|
|
2883
|
+
if (!file) return;
|
|
2884
|
+
setBusy(true);
|
|
2885
|
+
try {
|
|
2886
|
+
await arbi.selectWorkspace(workspaceId);
|
|
2887
|
+
const res = await arbi.documents.uploadFile(file, file.name, { folder });
|
|
2888
|
+
const id = res.doc_ext_ids?.[0];
|
|
2889
|
+
if (id) onChange(asAssetRef(id));
|
|
2890
|
+
} finally {
|
|
2891
|
+
setBusy(false);
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
const onGenerate = async () => {
|
|
2895
|
+
if (!prompt.trim() || gen.isGenerating) return;
|
|
2896
|
+
const id = await gen.generate(prompt);
|
|
2897
|
+
if (id) onChange(asAssetRef(id));
|
|
2898
|
+
};
|
|
2899
|
+
const MODES = [
|
|
2900
|
+
{ key: "asset", label: "Assets", icon: lucideReact.ImagePlus },
|
|
2901
|
+
{ key: "upload", label: "Upload", icon: lucideReact.Upload },
|
|
2902
|
+
{ key: "url", label: "URL", icon: lucideReact.Link2 },
|
|
2903
|
+
{ key: "generate", label: "Generate", icon: lucideReact.Sparkles }
|
|
2904
|
+
];
|
|
2905
|
+
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";
|
|
2906
|
+
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"}`;
|
|
2907
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", "data-testid": tp(), children: [
|
|
2908
|
+
/* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: value, aspect: "16:9", rounded: "md", testId: tp("preview") }),
|
|
2909
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: MODES.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2910
|
+
"button",
|
|
2911
|
+
{
|
|
2912
|
+
type: "button",
|
|
2913
|
+
className: tabClass(mode === m.key),
|
|
2914
|
+
onClick: () => setMode(m.key),
|
|
2915
|
+
"data-testid": tp("mode", m.key),
|
|
2916
|
+
children: [
|
|
2917
|
+
/* @__PURE__ */ jsxRuntime.jsx(m.icon, { className: "size-3" }),
|
|
2918
|
+
" ",
|
|
2919
|
+
m.label
|
|
2920
|
+
]
|
|
2921
|
+
},
|
|
2922
|
+
m.key
|
|
2923
|
+
)) }),
|
|
2924
|
+
mode === "asset" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid max-h-48 grid-cols-3 gap-1.5 overflow-auto", "data-testid": tp("assets"), children: [
|
|
2925
|
+
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." }),
|
|
2926
|
+
imageDocs.map((d) => {
|
|
2927
|
+
const thumb = thumbs?.[d.external_id];
|
|
2928
|
+
const selected = value === asAssetRef(d.external_id);
|
|
2929
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2930
|
+
"button",
|
|
2931
|
+
{
|
|
2932
|
+
type: "button",
|
|
2933
|
+
onClick: () => onChange(asAssetRef(d.external_id)),
|
|
2934
|
+
className: `overflow-hidden rounded border ${selected ? "border-primary ring-1 ring-primary" : "border-border"}`,
|
|
2935
|
+
title: d.file_name ?? "",
|
|
2936
|
+
"data-testid": tp("asset", d.external_id),
|
|
2937
|
+
children: thumb ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2938
|
+
"img",
|
|
2939
|
+
{
|
|
2940
|
+
src: thumb,
|
|
2941
|
+
alt: d.file_name ?? "",
|
|
2942
|
+
className: "aspect-square w-full object-cover"
|
|
2943
|
+
}
|
|
2944
|
+
) : /* @__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" }) })
|
|
2945
|
+
},
|
|
2946
|
+
d.external_id
|
|
2947
|
+
);
|
|
2948
|
+
})
|
|
2949
|
+
] }),
|
|
2950
|
+
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: [
|
|
2951
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "size-4" }),
|
|
2952
|
+
busy ? "Uploading\u2026" : "Choose a file",
|
|
2953
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2954
|
+
"input",
|
|
2955
|
+
{
|
|
2956
|
+
type: "file",
|
|
2957
|
+
accept: "image/*",
|
|
2958
|
+
className: "hidden",
|
|
2959
|
+
disabled: busy,
|
|
2960
|
+
onChange: (e) => void onUpload(e),
|
|
2961
|
+
"data-testid": tp("upload")
|
|
2962
|
+
}
|
|
2963
|
+
)
|
|
2964
|
+
] }),
|
|
2965
|
+
mode === "url" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
|
|
2966
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2967
|
+
"input",
|
|
2968
|
+
{
|
|
2969
|
+
className: inputClass,
|
|
2970
|
+
placeholder: "https://\u2026",
|
|
2971
|
+
value: url,
|
|
2972
|
+
onChange: (e) => setUrl(e.target.value),
|
|
2973
|
+
"data-testid": tp("url")
|
|
2974
|
+
}
|
|
2975
|
+
),
|
|
2976
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2977
|
+
"button",
|
|
2978
|
+
{
|
|
2979
|
+
type: "button",
|
|
2980
|
+
className: "rounded-md bg-primary px-2.5 py-1.5 text-sm font-medium text-primary-foreground disabled:opacity-50",
|
|
2981
|
+
disabled: !url.trim(),
|
|
2982
|
+
onClick: () => onChange(url.trim()),
|
|
2983
|
+
"data-testid": tp("url-apply"),
|
|
2984
|
+
children: "Use"
|
|
2985
|
+
}
|
|
2986
|
+
)
|
|
2987
|
+
] }),
|
|
2988
|
+
mode === "generate" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
2989
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2990
|
+
"textarea",
|
|
2991
|
+
{
|
|
2992
|
+
className: inputClass,
|
|
2993
|
+
rows: 2,
|
|
2994
|
+
placeholder: "Describe the image\u2026",
|
|
2995
|
+
value: prompt,
|
|
2996
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
2997
|
+
"data-testid": tp("prompt")
|
|
2998
|
+
}
|
|
2999
|
+
),
|
|
3000
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3001
|
+
"button",
|
|
3002
|
+
{
|
|
3003
|
+
type: "button",
|
|
3004
|
+
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",
|
|
3005
|
+
disabled: !prompt.trim() || gen.isGenerating,
|
|
3006
|
+
onClick: () => void onGenerate(),
|
|
3007
|
+
"data-testid": tp("generate"),
|
|
3008
|
+
children: [
|
|
3009
|
+
gen.isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-3.5" }),
|
|
3010
|
+
gen.isGenerating ? "Generating\u2026" : "Generate"
|
|
3011
|
+
]
|
|
3012
|
+
}
|
|
3013
|
+
),
|
|
3014
|
+
gen.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive", role: "alert", children: gen.error })
|
|
3015
|
+
] }),
|
|
3016
|
+
value && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3017
|
+
"button",
|
|
3018
|
+
{
|
|
3019
|
+
type: "button",
|
|
3020
|
+
className: "self-start text-xs text-muted-foreground hover:text-foreground",
|
|
3021
|
+
onClick: () => onChange(""),
|
|
3022
|
+
"data-testid": tp("clear"),
|
|
3023
|
+
children: "Clear"
|
|
3024
|
+
}
|
|
3025
|
+
)
|
|
3026
|
+
] });
|
|
3027
|
+
}
|
|
3028
|
+
function createImageField(cfg) {
|
|
3029
|
+
const { workspaceId } = cfg;
|
|
3030
|
+
const folder = cfg.folder ?? "assets";
|
|
3031
|
+
const testIdPrefix = cfg.testIdPrefix ?? "studio";
|
|
3032
|
+
const imageField = (label) => ({
|
|
3033
|
+
type: "custom",
|
|
3034
|
+
label,
|
|
3035
|
+
render: ({ value, onChange, name }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3036
|
+
ImageFieldControl,
|
|
3037
|
+
{
|
|
3038
|
+
value: value ?? "",
|
|
3039
|
+
onChange,
|
|
3040
|
+
name,
|
|
3041
|
+
workspaceId,
|
|
3042
|
+
folder,
|
|
3043
|
+
testIdPrefix
|
|
3044
|
+
}
|
|
3045
|
+
)
|
|
3046
|
+
});
|
|
3047
|
+
return { imageField };
|
|
3048
|
+
}
|
|
3049
|
+
var TEXTURE_STYLE = {
|
|
3050
|
+
backgroundImage: "radial-gradient(currentColor 0.5px, transparent 0.5px)",
|
|
3051
|
+
backgroundSize: "4px 4px",
|
|
3052
|
+
opacity: 0.04
|
|
3053
|
+
};
|
|
3054
|
+
var PADDING2 = {
|
|
3055
|
+
none: "",
|
|
3056
|
+
sm: "py-8 lg:py-10",
|
|
3057
|
+
md: "py-16 lg:py-20",
|
|
3058
|
+
lg: "py-24 lg:py-28"
|
|
3059
|
+
};
|
|
3060
|
+
var WIDTH2 = {
|
|
3061
|
+
narrow: "max-w-3xl",
|
|
3062
|
+
default: "max-w-7xl",
|
|
3063
|
+
wide: "max-w-[90rem]",
|
|
3064
|
+
full: "max-w-none"
|
|
3065
|
+
};
|
|
3066
|
+
var INVERTED_VARS = {
|
|
3067
|
+
["--color-background"]: "var(--color-foreground)",
|
|
3068
|
+
["--color-foreground"]: "var(--color-background)",
|
|
3069
|
+
["--color-card"]: "color-mix(in srgb, var(--color-foreground) 92%, #fff)",
|
|
3070
|
+
["--color-muted"]: "color-mix(in srgb, var(--color-foreground) 85%, #fff)",
|
|
3071
|
+
["--color-muted-foreground"]: "color-mix(in srgb, var(--color-background) 60%, transparent)",
|
|
3072
|
+
["--color-border"]: "color-mix(in srgb, var(--color-background) 18%, transparent)",
|
|
3073
|
+
["--color-accent"]: "color-mix(in srgb, var(--color-foreground) 80%, #fff)"
|
|
3074
|
+
};
|
|
3075
|
+
function Section({
|
|
3076
|
+
children,
|
|
3077
|
+
tone = "default",
|
|
3078
|
+
padding = "md",
|
|
3079
|
+
maxWidth = "default",
|
|
3080
|
+
texture,
|
|
3081
|
+
className = ""
|
|
3082
|
+
}) {
|
|
3083
|
+
const inverted = tone === "ink";
|
|
3084
|
+
const toneClass = inverted ? "bg-background text-foreground" : tone === "muted" ? "border-y border-border bg-card/40" : "";
|
|
3085
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: react.cn("relative", toneClass), style: inverted ? INVERTED_VARS : void 0, children: [
|
|
3086
|
+
texture && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0", style: TEXTURE_STYLE, "aria-hidden": true }),
|
|
3087
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3088
|
+
"div",
|
|
3089
|
+
{
|
|
3090
|
+
className: react.cn(
|
|
3091
|
+
"relative mx-auto px-5 lg:px-8",
|
|
3092
|
+
WIDTH2[maxWidth],
|
|
3093
|
+
PADDING2[padding],
|
|
3094
|
+
className
|
|
3095
|
+
),
|
|
3096
|
+
children
|
|
3097
|
+
}
|
|
3098
|
+
)
|
|
3099
|
+
] });
|
|
3100
|
+
}
|
|
3101
|
+
var iconMap = {
|
|
3102
|
+
FileText: lucideReact.FileText,
|
|
3103
|
+
Folder: lucideReact.Folder,
|
|
3104
|
+
Users: lucideReact.Users,
|
|
3105
|
+
Briefcase: lucideReact.Briefcase,
|
|
3106
|
+
Calendar: lucideReact.Calendar,
|
|
3107
|
+
Clock: lucideReact.Clock,
|
|
3108
|
+
CheckCircle: lucideReact.CheckCircle,
|
|
3109
|
+
AlertCircle: lucideReact.AlertCircle,
|
|
3110
|
+
Info: lucideReact.Info,
|
|
3111
|
+
TrendingUp: lucideReact.TrendingUp,
|
|
2251
3112
|
DollarSign: lucideReact.DollarSign,
|
|
2252
3113
|
BarChart3: lucideReact.BarChart3,
|
|
2253
3114
|
Activity: lucideReact.Activity,
|
|
@@ -2278,10 +3139,12 @@ var ICON_OPTIONS = [
|
|
|
2278
3139
|
{ label: "None", value: "" },
|
|
2279
3140
|
...Object.keys(iconMap).map((name) => ({ label: name, value: name }))
|
|
2280
3141
|
];
|
|
2281
|
-
function createBlockKit(ai) {
|
|
3142
|
+
function createBlockKit(ai, image, eyebrowClassName) {
|
|
2282
3143
|
return {
|
|
3144
|
+
eyebrowClassName,
|
|
2283
3145
|
textField: (label, placeholder) => ai ? ai.aiText(label, placeholder) : { type: "text", label, placeholder },
|
|
2284
3146
|
textareaField: (label, placeholder, kind) => ai ? ai.aiTextarea(label, placeholder, kind) : { type: "textarea", label, placeholder },
|
|
3147
|
+
imageField: (label) => image ? image(label) : { type: "text", label },
|
|
2285
3148
|
boolRadio: (label) => ({
|
|
2286
3149
|
type: "radio",
|
|
2287
3150
|
label,
|
|
@@ -2311,7 +3174,7 @@ var toneField = {
|
|
|
2311
3174
|
]
|
|
2312
3175
|
};
|
|
2313
3176
|
function createWebsiteBlocks(kit) {
|
|
2314
|
-
const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
|
|
3177
|
+
const { textField, textareaField, imageField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
|
|
2315
3178
|
const ctaField = (label) => ({
|
|
2316
3179
|
type: "object",
|
|
2317
3180
|
label,
|
|
@@ -2410,6 +3273,7 @@ function createWebsiteBlocks(kit) {
|
|
|
2410
3273
|
fields: {
|
|
2411
3274
|
eyebrow: textField("Eyebrow"),
|
|
2412
3275
|
title: textField("Title"),
|
|
3276
|
+
emphasis: { type: "text", label: "Emphasised word" },
|
|
2413
3277
|
subtitle: textareaField("Subtitle", void 0, "lead"),
|
|
2414
3278
|
primaryCta: ctaField("Primary CTA"),
|
|
2415
3279
|
secondaryCta: ctaField("Secondary CTA"),
|
|
@@ -2422,29 +3286,51 @@ function createWebsiteBlocks(kit) {
|
|
|
2422
3286
|
]
|
|
2423
3287
|
},
|
|
2424
3288
|
tone: toneField,
|
|
2425
|
-
imageUrl:
|
|
3289
|
+
imageUrl: imageField("Side image"),
|
|
3290
|
+
backgroundImage: imageField("Background image"),
|
|
3291
|
+
overlay: boolRadio("Background overlay")
|
|
2426
3292
|
},
|
|
2427
3293
|
defaultProps: {
|
|
2428
3294
|
eyebrow: "Introducing",
|
|
2429
3295
|
title: "Build a better website, faster.",
|
|
3296
|
+
emphasis: "",
|
|
2430
3297
|
subtitle: "A flexible platform that helps your team ship polished, on-brand pages without waiting on engineering.",
|
|
2431
3298
|
primaryCta: { label: "Get started", href: "#" },
|
|
2432
3299
|
secondaryCta: { label: "Learn more", href: "#" },
|
|
2433
3300
|
align: "left",
|
|
2434
3301
|
tone: "default",
|
|
2435
|
-
imageUrl: ""
|
|
3302
|
+
imageUrl: "",
|
|
3303
|
+
backgroundImage: "",
|
|
3304
|
+
overlay: true
|
|
2436
3305
|
},
|
|
2437
|
-
render: ({
|
|
3306
|
+
render: ({
|
|
3307
|
+
id,
|
|
3308
|
+
eyebrow,
|
|
3309
|
+
title,
|
|
3310
|
+
emphasis,
|
|
3311
|
+
subtitle,
|
|
3312
|
+
primaryCta,
|
|
3313
|
+
secondaryCta,
|
|
3314
|
+
align,
|
|
3315
|
+
tone,
|
|
3316
|
+
imageUrl,
|
|
3317
|
+
backgroundImage,
|
|
3318
|
+
overlay
|
|
3319
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2438
3320
|
Hero,
|
|
2439
3321
|
{
|
|
2440
3322
|
eyebrow,
|
|
2441
3323
|
title,
|
|
3324
|
+
emphasis,
|
|
2442
3325
|
subtitle,
|
|
2443
3326
|
primaryCta,
|
|
2444
3327
|
secondaryCta,
|
|
2445
3328
|
align,
|
|
2446
3329
|
tone,
|
|
2447
3330
|
imageUrl,
|
|
3331
|
+
backgroundImage,
|
|
3332
|
+
overlay,
|
|
3333
|
+
eyebrowClassName: kit.eyebrowClassName,
|
|
2448
3334
|
testId: blockTestId(id)
|
|
2449
3335
|
}
|
|
2450
3336
|
)
|
|
@@ -2561,15 +3447,30 @@ function createWebsiteBlocks(kit) {
|
|
|
2561
3447
|
title: textField("Title"),
|
|
2562
3448
|
subtitle: textareaField("Subtitle"),
|
|
2563
3449
|
cta: ctaField("Button"),
|
|
2564
|
-
tone: toneField
|
|
3450
|
+
tone: toneField,
|
|
3451
|
+
backgroundImage: imageField("Background image"),
|
|
3452
|
+
overlay: boolRadio("Background overlay")
|
|
2565
3453
|
},
|
|
2566
3454
|
defaultProps: {
|
|
2567
3455
|
title: "Ready to get started?",
|
|
2568
3456
|
subtitle: "Join thousands of teams already building with us.",
|
|
2569
3457
|
cta: { label: "Start free trial", href: "#" },
|
|
2570
|
-
tone: "default"
|
|
3458
|
+
tone: "default",
|
|
3459
|
+
backgroundImage: "",
|
|
3460
|
+
overlay: true
|
|
2571
3461
|
},
|
|
2572
|
-
render: ({ id, title, subtitle, cta, tone }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3462
|
+
render: ({ id, title, subtitle, cta, tone, backgroundImage, overlay }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3463
|
+
CTABanner,
|
|
3464
|
+
{
|
|
3465
|
+
title,
|
|
3466
|
+
subtitle,
|
|
3467
|
+
cta,
|
|
3468
|
+
tone,
|
|
3469
|
+
backgroundImage,
|
|
3470
|
+
overlay,
|
|
3471
|
+
testId: blockTestId(id)
|
|
3472
|
+
}
|
|
3473
|
+
)
|
|
2573
3474
|
};
|
|
2574
3475
|
const LogoCloudBlock = {
|
|
2575
3476
|
label: "Logo cloud",
|
|
@@ -2661,7 +3562,7 @@ function createWebsiteBlocks(kit) {
|
|
|
2661
3562
|
const ImageBlock = {
|
|
2662
3563
|
label: "Image",
|
|
2663
3564
|
fields: {
|
|
2664
|
-
src:
|
|
3565
|
+
src: imageField("Image"),
|
|
2665
3566
|
alt: textField("Alt text"),
|
|
2666
3567
|
aspect: {
|
|
2667
3568
|
type: "radio",
|
|
@@ -2682,10 +3583,21 @@ function createWebsiteBlocks(kit) {
|
|
|
2682
3583
|
{ label: "L", value: "lg" },
|
|
2683
3584
|
{ label: "Full", value: "full" }
|
|
2684
3585
|
]
|
|
2685
|
-
}
|
|
3586
|
+
},
|
|
3587
|
+
framed: boolRadio("Framed")
|
|
2686
3588
|
},
|
|
2687
|
-
defaultProps: { src: "", alt: "Image", aspect: "16:9", rounded: "lg" },
|
|
2688
|
-
render: ({ id, src, alt, aspect, rounded }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3589
|
+
defaultProps: { src: "", alt: "Image", aspect: "16:9", rounded: "lg", framed: false },
|
|
3590
|
+
render: ({ id, src, alt, aspect, rounded, framed }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3591
|
+
BlockImage,
|
|
3592
|
+
{
|
|
3593
|
+
src,
|
|
3594
|
+
alt,
|
|
3595
|
+
aspect,
|
|
3596
|
+
rounded,
|
|
3597
|
+
framed,
|
|
3598
|
+
testId: blockTestId(id)
|
|
3599
|
+
}
|
|
3600
|
+
)
|
|
2689
3601
|
};
|
|
2690
3602
|
const AvatarBlockConfig = {
|
|
2691
3603
|
label: "Avatar",
|
|
@@ -2831,8 +3743,440 @@ function createWebsiteBlocks(kit) {
|
|
|
2831
3743
|
List: ListBlockConfig
|
|
2832
3744
|
};
|
|
2833
3745
|
}
|
|
3746
|
+
var lines = (s) => (s ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3747
|
+
function createSiteBlocks(kit) {
|
|
3748
|
+
const { textField, textareaField, imageField, boolRadio, blockTestId } = kit;
|
|
3749
|
+
const ctaField = (label) => ({
|
|
3750
|
+
type: "object",
|
|
3751
|
+
label,
|
|
3752
|
+
objectFields: {
|
|
3753
|
+
label: textField("Label"),
|
|
3754
|
+
href: { type: "text", label: "Link" }
|
|
3755
|
+
}
|
|
3756
|
+
});
|
|
3757
|
+
const columnsField = (values) => ({
|
|
3758
|
+
type: "radio",
|
|
3759
|
+
label: "Columns",
|
|
3760
|
+
options: values.map((v) => ({ label: v, value: v }))
|
|
3761
|
+
});
|
|
3762
|
+
const PricingBlock = {
|
|
3763
|
+
label: "Pricing",
|
|
3764
|
+
fields: {
|
|
3765
|
+
plans: {
|
|
3766
|
+
type: "array",
|
|
3767
|
+
label: "Plans",
|
|
3768
|
+
getItemSummary: (item) => item?.name || "Plan",
|
|
3769
|
+
defaultItemProps: {
|
|
3770
|
+
name: "Plan",
|
|
3771
|
+
price: "\xA30",
|
|
3772
|
+
period: "mo",
|
|
3773
|
+
description: "",
|
|
3774
|
+
features: "Feature one\nFeature two",
|
|
3775
|
+
ctaLabel: "Choose",
|
|
3776
|
+
ctaHref: "#",
|
|
3777
|
+
featured: false
|
|
3778
|
+
},
|
|
3779
|
+
arrayFields: {
|
|
3780
|
+
name: textField("Name"),
|
|
3781
|
+
price: { type: "text", label: "Price" },
|
|
3782
|
+
period: { type: "text", label: "Period" },
|
|
3783
|
+
description: textareaField("Description"),
|
|
3784
|
+
features: { type: "textarea", label: "Features (one per line)" },
|
|
3785
|
+
ctaLabel: { type: "text", label: "Button label" },
|
|
3786
|
+
ctaHref: { type: "text", label: "Button link" },
|
|
3787
|
+
featured: boolRadio("Featured")
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
},
|
|
3791
|
+
defaultProps: {
|
|
3792
|
+
plans: [
|
|
3793
|
+
{
|
|
3794
|
+
name: "Starter",
|
|
3795
|
+
price: "\xA30",
|
|
3796
|
+
period: "mo",
|
|
3797
|
+
description: "For getting started.",
|
|
3798
|
+
features: "Up to 3 projects\nCommunity support",
|
|
3799
|
+
ctaLabel: "Get started",
|
|
3800
|
+
ctaHref: "#",
|
|
3801
|
+
featured: false
|
|
3802
|
+
},
|
|
3803
|
+
{
|
|
3804
|
+
name: "Pro",
|
|
3805
|
+
price: "\xA329",
|
|
3806
|
+
period: "mo",
|
|
3807
|
+
description: "For growing teams.",
|
|
3808
|
+
features: "Unlimited projects\nPriority support\nAdvanced analytics",
|
|
3809
|
+
ctaLabel: "Start free trial",
|
|
3810
|
+
ctaHref: "#",
|
|
3811
|
+
featured: true
|
|
3812
|
+
},
|
|
3813
|
+
{
|
|
3814
|
+
name: "Enterprise",
|
|
3815
|
+
price: "Custom",
|
|
3816
|
+
period: "",
|
|
3817
|
+
description: "For large organisations.",
|
|
3818
|
+
features: "SSO & SAML\nDedicated success manager\nSLA",
|
|
3819
|
+
ctaLabel: "Contact sales",
|
|
3820
|
+
ctaHref: "#",
|
|
3821
|
+
featured: false
|
|
3822
|
+
}
|
|
3823
|
+
]
|
|
3824
|
+
},
|
|
3825
|
+
render: ({ id, plans }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3826
|
+
PricingTable,
|
|
3827
|
+
{
|
|
3828
|
+
plans: (plans ?? []).map(
|
|
3829
|
+
(p) => ({
|
|
3830
|
+
name: p.name,
|
|
3831
|
+
price: p.price,
|
|
3832
|
+
period: p.period,
|
|
3833
|
+
description: p.description,
|
|
3834
|
+
features: lines(p.features),
|
|
3835
|
+
cta: { label: p.ctaLabel, href: p.ctaHref },
|
|
3836
|
+
featured: p.featured
|
|
3837
|
+
})
|
|
3838
|
+
),
|
|
3839
|
+
testId: blockTestId(id)
|
|
3840
|
+
}
|
|
3841
|
+
)
|
|
3842
|
+
};
|
|
3843
|
+
const TabsBlock = {
|
|
3844
|
+
label: "Tabs",
|
|
3845
|
+
fields: {
|
|
3846
|
+
items: {
|
|
3847
|
+
type: "array",
|
|
3848
|
+
label: "Tabs",
|
|
3849
|
+
getItemSummary: (item) => item?.label || "Tab",
|
|
3850
|
+
defaultItemProps: { label: "Tab", content: "Tab content." },
|
|
3851
|
+
arrayFields: {
|
|
3852
|
+
label: textField("Label"),
|
|
3853
|
+
content: textareaField("Content")
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
},
|
|
3857
|
+
defaultProps: {
|
|
3858
|
+
items: [
|
|
3859
|
+
{ label: "Overview", content: "A quick overview of this section." },
|
|
3860
|
+
{ label: "Details", content: "The finer details go here." },
|
|
3861
|
+
{ label: "FAQ", content: "Answers to common questions." }
|
|
3862
|
+
]
|
|
3863
|
+
},
|
|
3864
|
+
render: ({ id, items }) => /* @__PURE__ */ jsxRuntime.jsx(Tabs, { items, testId: blockTestId(id) })
|
|
3865
|
+
};
|
|
3866
|
+
const AccordionBlock = {
|
|
3867
|
+
label: "Accordion",
|
|
3868
|
+
fields: {
|
|
3869
|
+
allowMultiple: boolRadio("Allow multiple open"),
|
|
3870
|
+
items: {
|
|
3871
|
+
type: "array",
|
|
3872
|
+
label: "Items",
|
|
3873
|
+
getItemSummary: (item) => item?.title || "Item",
|
|
3874
|
+
defaultItemProps: { title: "Section", content: "Section content." },
|
|
3875
|
+
arrayFields: {
|
|
3876
|
+
title: textField("Title"),
|
|
3877
|
+
content: textareaField("Content")
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
},
|
|
3881
|
+
defaultProps: {
|
|
3882
|
+
allowMultiple: false,
|
|
3883
|
+
items: [
|
|
3884
|
+
{ title: "What is included?", content: "Everything you need to get started." },
|
|
3885
|
+
{ title: "How does billing work?", content: "Monthly or annually, cancel anytime." }
|
|
3886
|
+
]
|
|
3887
|
+
},
|
|
3888
|
+
render: ({ id, allowMultiple, items }) => /* @__PURE__ */ jsxRuntime.jsx(Accordion, { allowMultiple, items, testId: blockTestId(id) })
|
|
3889
|
+
};
|
|
3890
|
+
const GalleryBlock = {
|
|
3891
|
+
label: "Gallery",
|
|
3892
|
+
fields: {
|
|
3893
|
+
columns: columnsField(["2", "3", "4"]),
|
|
3894
|
+
aspect: {
|
|
3895
|
+
type: "radio",
|
|
3896
|
+
label: "Aspect",
|
|
3897
|
+
options: [
|
|
3898
|
+
{ label: "1:1", value: "1:1" },
|
|
3899
|
+
{ label: "4:3", value: "4:3" },
|
|
3900
|
+
{ label: "16:9", value: "16:9" }
|
|
3901
|
+
]
|
|
3902
|
+
},
|
|
3903
|
+
images: {
|
|
3904
|
+
type: "array",
|
|
3905
|
+
label: "Images",
|
|
3906
|
+
getItemSummary: (item) => item?.alt || "Image",
|
|
3907
|
+
defaultItemProps: { src: "", alt: "Image", caption: "" },
|
|
3908
|
+
arrayFields: {
|
|
3909
|
+
src: imageField("Image"),
|
|
3910
|
+
alt: { type: "text", label: "Alt text" },
|
|
3911
|
+
caption: { type: "text", label: "Caption" }
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
},
|
|
3915
|
+
defaultProps: {
|
|
3916
|
+
columns: "3",
|
|
3917
|
+
aspect: "1:1",
|
|
3918
|
+
images: [
|
|
3919
|
+
{ src: "", alt: "Image one", caption: "" },
|
|
3920
|
+
{ src: "", alt: "Image two", caption: "" },
|
|
3921
|
+
{ src: "", alt: "Image three", caption: "" }
|
|
3922
|
+
]
|
|
3923
|
+
},
|
|
3924
|
+
render: ({ id, columns, aspect, images }) => /* @__PURE__ */ jsxRuntime.jsx(Gallery, { columns, aspect, images, testId: blockTestId(id) })
|
|
3925
|
+
};
|
|
3926
|
+
const VideoBlock = {
|
|
3927
|
+
label: "Video",
|
|
3928
|
+
fields: {
|
|
3929
|
+
url: { type: "text", label: "Video URL (YouTube/Vimeo/Loom or file)" },
|
|
3930
|
+
title: { type: "text", label: "Title" },
|
|
3931
|
+
aspect: {
|
|
3932
|
+
type: "radio",
|
|
3933
|
+
label: "Aspect",
|
|
3934
|
+
options: [
|
|
3935
|
+
{ label: "16:9", value: "16:9" },
|
|
3936
|
+
{ label: "4:3", value: "4:3" },
|
|
3937
|
+
{ label: "1:1", value: "1:1" }
|
|
3938
|
+
]
|
|
3939
|
+
},
|
|
3940
|
+
rounded: boolRadio("Rounded")
|
|
3941
|
+
},
|
|
3942
|
+
defaultProps: { url: "", title: "", aspect: "16:9", rounded: true },
|
|
3943
|
+
render: ({ id, url, title, aspect, rounded }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3944
|
+
VideoEmbed,
|
|
3945
|
+
{
|
|
3946
|
+
url,
|
|
3947
|
+
title,
|
|
3948
|
+
aspect,
|
|
3949
|
+
rounded,
|
|
3950
|
+
testId: blockTestId(id)
|
|
3951
|
+
}
|
|
3952
|
+
)
|
|
3953
|
+
};
|
|
3954
|
+
const ContactFormBlock = {
|
|
3955
|
+
label: "Contact form",
|
|
3956
|
+
fields: {
|
|
3957
|
+
submitLabel: { type: "text", label: "Submit label" },
|
|
3958
|
+
action: { type: "text", label: "POST endpoint (optional)" },
|
|
3959
|
+
successMessage: textareaField("Success message"),
|
|
3960
|
+
fields: {
|
|
3961
|
+
type: "array",
|
|
3962
|
+
label: "Fields",
|
|
3963
|
+
getItemSummary: (item) => item?.label || "Field",
|
|
3964
|
+
defaultItemProps: { name: "field", label: "Field", type: "text", required: false },
|
|
3965
|
+
arrayFields: {
|
|
3966
|
+
name: { type: "text", label: "Name attribute" },
|
|
3967
|
+
label: textField("Label"),
|
|
3968
|
+
type: {
|
|
3969
|
+
type: "select",
|
|
3970
|
+
label: "Type",
|
|
3971
|
+
options: [
|
|
3972
|
+
{ label: "Text", value: "text" },
|
|
3973
|
+
{ label: "Email", value: "email" },
|
|
3974
|
+
{ label: "Phone", value: "tel" },
|
|
3975
|
+
{ label: "Multi-line", value: "textarea" }
|
|
3976
|
+
]
|
|
3977
|
+
},
|
|
3978
|
+
required: boolRadio("Required")
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
},
|
|
3982
|
+
defaultProps: {
|
|
3983
|
+
submitLabel: "Send",
|
|
3984
|
+
action: "",
|
|
3985
|
+
successMessage: "Thanks \u2014 we\u2019ll be in touch shortly.",
|
|
3986
|
+
fields: [
|
|
3987
|
+
{ name: "name", label: "Name", type: "text", required: true },
|
|
3988
|
+
{ name: "email", label: "Email", type: "email", required: true },
|
|
3989
|
+
{ name: "message", label: "Message", type: "textarea", required: true }
|
|
3990
|
+
]
|
|
3991
|
+
},
|
|
3992
|
+
render: ({ id, submitLabel, action, successMessage, fields }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3993
|
+
ContactForm,
|
|
3994
|
+
{
|
|
3995
|
+
submitLabel,
|
|
3996
|
+
action: action || void 0,
|
|
3997
|
+
successMessage,
|
|
3998
|
+
fields,
|
|
3999
|
+
testId: blockTestId(id)
|
|
4000
|
+
}
|
|
4001
|
+
)
|
|
4002
|
+
};
|
|
4003
|
+
const BannerBlock = {
|
|
4004
|
+
label: "Banner",
|
|
4005
|
+
fields: {
|
|
4006
|
+
text: textField("Text"),
|
|
4007
|
+
cta: ctaField("CTA"),
|
|
4008
|
+
tone: {
|
|
4009
|
+
type: "radio",
|
|
4010
|
+
label: "Tone",
|
|
4011
|
+
options: [
|
|
4012
|
+
{ label: "Primary", value: "primary" },
|
|
4013
|
+
{ label: "Muted", value: "muted" },
|
|
4014
|
+
{ label: "Dark", value: "ink" }
|
|
4015
|
+
]
|
|
4016
|
+
}
|
|
4017
|
+
},
|
|
4018
|
+
defaultProps: {
|
|
4019
|
+
text: "We just shipped something new.",
|
|
4020
|
+
cta: { label: "Read more", href: "#" },
|
|
4021
|
+
tone: "primary"
|
|
4022
|
+
},
|
|
4023
|
+
render: ({ id, text, cta, tone }) => /* @__PURE__ */ jsxRuntime.jsx(Banner, { text, cta, tone, testId: blockTestId(id) })
|
|
4024
|
+
};
|
|
4025
|
+
const MediaTextBlock = {
|
|
4026
|
+
label: "Media & text",
|
|
4027
|
+
fields: {
|
|
4028
|
+
eyebrow: textField("Eyebrow"),
|
|
4029
|
+
title: textField("Title"),
|
|
4030
|
+
body: textareaField("Body", void 0, "lead"),
|
|
4031
|
+
imageUrl: imageField("Image"),
|
|
4032
|
+
mediaSide: {
|
|
4033
|
+
type: "radio",
|
|
4034
|
+
label: "Media side",
|
|
4035
|
+
options: [
|
|
4036
|
+
{ label: "Left", value: "left" },
|
|
4037
|
+
{ label: "Right", value: "right" }
|
|
4038
|
+
]
|
|
4039
|
+
},
|
|
4040
|
+
cta: ctaField("CTA")
|
|
4041
|
+
},
|
|
4042
|
+
defaultProps: {
|
|
4043
|
+
eyebrow: "",
|
|
4044
|
+
title: "A closer look at what we do.",
|
|
4045
|
+
body: "Pair a compelling visual with the copy that explains it \u2014 the classic split section, on any theme.",
|
|
4046
|
+
imageUrl: "",
|
|
4047
|
+
mediaSide: "left",
|
|
4048
|
+
cta: { label: "Learn more", href: "#" }
|
|
4049
|
+
},
|
|
4050
|
+
render: ({ id, eyebrow, title, body, imageUrl, mediaSide, cta }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4051
|
+
MediaText,
|
|
4052
|
+
{
|
|
4053
|
+
eyebrow,
|
|
4054
|
+
title,
|
|
4055
|
+
body,
|
|
4056
|
+
imageUrl,
|
|
4057
|
+
mediaSide,
|
|
4058
|
+
cta,
|
|
4059
|
+
testId: blockTestId(id)
|
|
4060
|
+
}
|
|
4061
|
+
)
|
|
4062
|
+
};
|
|
4063
|
+
const TeamGridBlock = {
|
|
4064
|
+
label: "Team",
|
|
4065
|
+
fields: {
|
|
4066
|
+
columns: columnsField(["2", "3", "4"]),
|
|
4067
|
+
members: {
|
|
4068
|
+
type: "array",
|
|
4069
|
+
label: "Members",
|
|
4070
|
+
getItemSummary: (item) => item?.name || "Member",
|
|
4071
|
+
defaultItemProps: { name: "New member", role: "Role", bio: "", avatarUrl: "" },
|
|
4072
|
+
arrayFields: {
|
|
4073
|
+
name: textField("Name"),
|
|
4074
|
+
role: { type: "text", label: "Role" },
|
|
4075
|
+
bio: textareaField("Bio"),
|
|
4076
|
+
avatarUrl: { type: "text", label: "Avatar URL" }
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
},
|
|
4080
|
+
defaultProps: {
|
|
4081
|
+
columns: "3",
|
|
4082
|
+
members: [
|
|
4083
|
+
{ name: "Alex Morgan", role: "Founder & CEO", bio: "", avatarUrl: "" },
|
|
4084
|
+
{ name: "Sam Rivera", role: "Head of Product", bio: "", avatarUrl: "" },
|
|
4085
|
+
{ name: "Jordan Lee", role: "Lead Engineer", bio: "", avatarUrl: "" }
|
|
4086
|
+
]
|
|
4087
|
+
},
|
|
4088
|
+
render: ({ id, columns, members }) => /* @__PURE__ */ jsxRuntime.jsx(TeamGrid, { columns, members, testId: blockTestId(id) })
|
|
4089
|
+
};
|
|
4090
|
+
const StepsBlock = {
|
|
4091
|
+
label: "Steps",
|
|
4092
|
+
fields: {
|
|
4093
|
+
variant: {
|
|
4094
|
+
type: "radio",
|
|
4095
|
+
label: "Style",
|
|
4096
|
+
options: [
|
|
4097
|
+
{ label: "Numbered", value: "numbered" },
|
|
4098
|
+
{ label: "Timeline", value: "timeline" }
|
|
4099
|
+
]
|
|
4100
|
+
},
|
|
4101
|
+
steps: {
|
|
4102
|
+
type: "array",
|
|
4103
|
+
label: "Steps",
|
|
4104
|
+
getItemSummary: (item) => item?.title || "Step",
|
|
4105
|
+
defaultItemProps: { title: "Step", description: "Describe this step." },
|
|
4106
|
+
arrayFields: {
|
|
4107
|
+
title: textField("Title"),
|
|
4108
|
+
description: textareaField("Description")
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
},
|
|
4112
|
+
defaultProps: {
|
|
4113
|
+
variant: "numbered",
|
|
4114
|
+
steps: [
|
|
4115
|
+
{ title: "Sign up", description: "Create your account in seconds." },
|
|
4116
|
+
{ title: "Configure", description: "Set things up to match your workflow." },
|
|
4117
|
+
{ title: "Launch", description: "Go live and start seeing results." }
|
|
4118
|
+
]
|
|
4119
|
+
},
|
|
4120
|
+
render: ({ id, variant, steps }) => /* @__PURE__ */ jsxRuntime.jsx(Steps, { variant, steps, testId: blockTestId(id) })
|
|
4121
|
+
};
|
|
4122
|
+
const TestimonialGridBlock = {
|
|
4123
|
+
label: "Testimonial grid",
|
|
4124
|
+
fields: {
|
|
4125
|
+
columns: columnsField(["2", "3"]),
|
|
4126
|
+
items: {
|
|
4127
|
+
type: "array",
|
|
4128
|
+
label: "Testimonials",
|
|
4129
|
+
getItemSummary: (item) => item?.author || "Testimonial",
|
|
4130
|
+
defaultItemProps: { quote: "A great quote.", author: "Author", role: "", avatarUrl: "" },
|
|
4131
|
+
arrayFields: {
|
|
4132
|
+
quote: textareaField("Quote"),
|
|
4133
|
+
author: textField("Author"),
|
|
4134
|
+
role: { type: "text", label: "Role" },
|
|
4135
|
+
avatarUrl: { type: "text", label: "Avatar URL" }
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
},
|
|
4139
|
+
defaultProps: {
|
|
4140
|
+
columns: "3",
|
|
4141
|
+
items: [
|
|
4142
|
+
{
|
|
4143
|
+
quote: "This changed how our whole team works.",
|
|
4144
|
+
author: "Alex Morgan",
|
|
4145
|
+
role: "CTO, Northwind",
|
|
4146
|
+
avatarUrl: ""
|
|
4147
|
+
},
|
|
4148
|
+
{
|
|
4149
|
+
quote: "The best tool we adopted this year.",
|
|
4150
|
+
author: "Sam Rivera",
|
|
4151
|
+
role: "Ops Lead, Acme",
|
|
4152
|
+
avatarUrl: ""
|
|
4153
|
+
},
|
|
4154
|
+
{
|
|
4155
|
+
quote: "Simple, fast, and it just works.",
|
|
4156
|
+
author: "Jordan Lee",
|
|
4157
|
+
role: "Founder, Globex",
|
|
4158
|
+
avatarUrl: ""
|
|
4159
|
+
}
|
|
4160
|
+
]
|
|
4161
|
+
},
|
|
4162
|
+
render: ({ id, columns, items }) => /* @__PURE__ */ jsxRuntime.jsx(TestimonialGrid, { columns, items, testId: blockTestId(id) })
|
|
4163
|
+
};
|
|
4164
|
+
return {
|
|
4165
|
+
Pricing: PricingBlock,
|
|
4166
|
+
Tabs: TabsBlock,
|
|
4167
|
+
Accordion: AccordionBlock,
|
|
4168
|
+
Gallery: GalleryBlock,
|
|
4169
|
+
Video: VideoBlock,
|
|
4170
|
+
ContactForm: ContactFormBlock,
|
|
4171
|
+
Banner: BannerBlock,
|
|
4172
|
+
MediaText: MediaTextBlock,
|
|
4173
|
+
Team: TeamGridBlock,
|
|
4174
|
+
Steps: StepsBlock,
|
|
4175
|
+
TestimonialGrid: TestimonialGridBlock
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
2834
4178
|
function createArbiBlocksConfig(opts = {}) {
|
|
2835
|
-
const kit = createBlockKit(opts.aiFields);
|
|
4179
|
+
const kit = createBlockKit(opts.aiFields, opts.imageField, opts.eyebrowClassName);
|
|
2836
4180
|
const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
|
|
2837
4181
|
const SectionHeadingBlock = {
|
|
2838
4182
|
label: "Section heading",
|
|
@@ -2851,7 +4195,17 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
2851
4195
|
invert: boolRadio("Invert (on dark)")
|
|
2852
4196
|
},
|
|
2853
4197
|
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(
|
|
4198
|
+
render: ({ id, title, eyebrow, lead, align, invert }) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4199
|
+
SectionHeading,
|
|
4200
|
+
{
|
|
4201
|
+
title,
|
|
4202
|
+
eyebrow,
|
|
4203
|
+
lead,
|
|
4204
|
+
align,
|
|
4205
|
+
invert,
|
|
4206
|
+
eyebrowClassName: kit.eyebrowClassName
|
|
4207
|
+
}
|
|
4208
|
+
) })
|
|
2855
4209
|
};
|
|
2856
4210
|
const PageHeaderBlock = {
|
|
2857
4211
|
label: "Page header",
|
|
@@ -2901,9 +4255,10 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
2901
4255
|
type: "radio",
|
|
2902
4256
|
label: "Accent",
|
|
2903
4257
|
options: [
|
|
2904
|
-
{ label: "
|
|
2905
|
-
{ label: "
|
|
2906
|
-
{ label: "
|
|
4258
|
+
{ label: "Default", value: "default" },
|
|
4259
|
+
{ label: "Primary", value: "primary" },
|
|
4260
|
+
{ label: "Success", value: "success" },
|
|
4261
|
+
{ label: "Muted", value: "muted" }
|
|
2907
4262
|
]
|
|
2908
4263
|
},
|
|
2909
4264
|
delta: {
|
|
@@ -2924,7 +4279,7 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
2924
4279
|
},
|
|
2925
4280
|
icon: iconField()
|
|
2926
4281
|
},
|
|
2927
|
-
defaultProps: { label: "Metric", value: "0", accent: "
|
|
4282
|
+
defaultProps: { label: "Metric", value: "0", accent: "default" },
|
|
2928
4283
|
render: ({ id, label, value, hint, accent, delta, icon }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2929
4284
|
MetricCard,
|
|
2930
4285
|
{
|
|
@@ -3027,12 +4382,39 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3027
4382
|
options: [
|
|
3028
4383
|
{ label: "Default", value: "default" },
|
|
3029
4384
|
{ label: "Muted", value: "muted" },
|
|
3030
|
-
{ label: "
|
|
4385
|
+
{ label: "Dark", value: "ink" }
|
|
3031
4386
|
]
|
|
3032
|
-
}
|
|
4387
|
+
},
|
|
4388
|
+
padding: {
|
|
4389
|
+
type: "radio",
|
|
4390
|
+
label: "Padding",
|
|
4391
|
+
options: [
|
|
4392
|
+
{ label: "None", value: "none" },
|
|
4393
|
+
{ label: "S", value: "sm" },
|
|
4394
|
+
{ label: "M", value: "md" },
|
|
4395
|
+
{ label: "L", value: "lg" }
|
|
4396
|
+
]
|
|
4397
|
+
},
|
|
4398
|
+
maxWidth: {
|
|
4399
|
+
type: "radio",
|
|
4400
|
+
label: "Width",
|
|
4401
|
+
options: [
|
|
4402
|
+
{ label: "Narrow", value: "narrow" },
|
|
4403
|
+
{ label: "Default", value: "default" },
|
|
4404
|
+
{ label: "Wide", value: "wide" },
|
|
4405
|
+
{ label: "Full", value: "full" }
|
|
4406
|
+
]
|
|
4407
|
+
},
|
|
4408
|
+
texture: boolRadio("Texture")
|
|
4409
|
+
},
|
|
4410
|
+
defaultProps: {
|
|
4411
|
+
tone: "default",
|
|
4412
|
+
padding: "md",
|
|
4413
|
+
maxWidth: "default",
|
|
4414
|
+
texture: false,
|
|
4415
|
+
children: []
|
|
3033
4416
|
},
|
|
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, {}) }) })
|
|
4417
|
+
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
4418
|
};
|
|
3037
4419
|
const CardBlock = {
|
|
3038
4420
|
label: "Card",
|
|
@@ -3108,6 +4490,7 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3108
4490
|
render: ({ id }) => /* @__PURE__ */ jsxRuntime.jsx(react.Separator, { "data-testid": blockTestId(id) })
|
|
3109
4491
|
};
|
|
3110
4492
|
const website = createWebsiteBlocks(kit);
|
|
4493
|
+
const site = createSiteBlocks(kit);
|
|
3111
4494
|
const components = {
|
|
3112
4495
|
SectionHeading: SectionHeadingBlock,
|
|
3113
4496
|
PageHeader: PageHeaderBlock,
|
|
@@ -3119,7 +4502,8 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3119
4502
|
Button: ButtonBlock,
|
|
3120
4503
|
Badge: BadgeBlock,
|
|
3121
4504
|
Separator: SeparatorBlock,
|
|
3122
|
-
...website
|
|
4505
|
+
...website,
|
|
4506
|
+
...site
|
|
3123
4507
|
};
|
|
3124
4508
|
const config = {
|
|
3125
4509
|
categories: {
|
|
@@ -3134,16 +4518,24 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3134
4518
|
"FeatureGrid",
|
|
3135
4519
|
"StatGroup",
|
|
3136
4520
|
"Testimonial",
|
|
4521
|
+
"TestimonialGrid",
|
|
3137
4522
|
"CTABanner",
|
|
4523
|
+
"Banner",
|
|
3138
4524
|
"LogoCloud",
|
|
3139
|
-
"FAQ"
|
|
4525
|
+
"FAQ",
|
|
4526
|
+
"MediaText",
|
|
4527
|
+
"Steps",
|
|
4528
|
+
"Team"
|
|
3140
4529
|
]
|
|
3141
4530
|
},
|
|
3142
4531
|
Content: {
|
|
3143
4532
|
title: "Content",
|
|
3144
4533
|
components: ["SectionHeading", "PageHeader", "EmptyState", "RichText", "Callout", "List"]
|
|
3145
4534
|
},
|
|
3146
|
-
|
|
4535
|
+
Interactive: { title: "Interactive", components: ["Tabs", "Accordion"] },
|
|
4536
|
+
Media: { title: "Media", components: ["Image", "Avatar", "Gallery", "Video"] },
|
|
4537
|
+
Commerce: { title: "Commerce", components: ["Pricing"] },
|
|
4538
|
+
Forms: { title: "Forms", components: ["ContactForm"] },
|
|
3147
4539
|
Navigation: { title: "Navigation", components: ["Navbar", "Footer"] },
|
|
3148
4540
|
Data: { title: "Data", components: ["MetricCard", "DataTable"] },
|
|
3149
4541
|
Primitives: { title: "Primitives", components: ["Button", "Badge", "Separator"] }
|
|
@@ -3153,8 +4545,8 @@ function createArbiBlocksConfig(opts = {}) {
|
|
|
3153
4545
|
render: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3154
4546
|
"div",
|
|
3155
4547
|
{
|
|
3156
|
-
className: "bg-background text-foreground font-sans",
|
|
3157
|
-
"data-testid": "arbi-blocks-page-root",
|
|
4548
|
+
className: react.cn("bg-background text-foreground font-sans", opts.root?.className),
|
|
4549
|
+
"data-testid": opts.root?.testId ?? "arbi-blocks-page-root",
|
|
3158
4550
|
children
|
|
3159
4551
|
}
|
|
3160
4552
|
)
|
|
@@ -3394,7 +4786,7 @@ function ModuleManager({ store, testIdPrefix = "builder" }) {
|
|
|
3394
4786
|
] }),
|
|
3395
4787
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
3396
4788
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3397
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-
|
|
4789
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: m.label }),
|
|
3398
4790
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: m.to })
|
|
3399
4791
|
] }),
|
|
3400
4792
|
m.arbi && /* @__PURE__ */ jsxRuntime.jsxs(react.Badge, { variant: "accent", className: "text-[10px]", children: [
|
|
@@ -3465,7 +4857,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3465
4857
|
"data-testid": `${testIdPrefix}-agents-configs`,
|
|
3466
4858
|
children: [
|
|
3467
4859
|
/* @__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-
|
|
4860
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.BrainCircuit, { className: "size-4 text-primary" }),
|
|
3469
4861
|
" Behaviour configurations",
|
|
3470
4862
|
/* @__PURE__ */ jsxRuntime.jsx(react.Badge, { variant: "secondary", className: "ml-1", children: versions.length })
|
|
3471
4863
|
] }) }),
|
|
@@ -3487,7 +4879,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3487
4879
|
}
|
|
3488
4880
|
),
|
|
3489
4881
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3490
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-
|
|
4882
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: v.title ?? "Untitled behaviour" }),
|
|
3491
4883
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: v.external_id })
|
|
3492
4884
|
] }),
|
|
3493
4885
|
/* @__PURE__ */ jsxRuntime.jsx(ConfigModelBadge, { configId: v.external_id, testIdPrefix })
|
|
@@ -3501,7 +4893,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3501
4893
|
),
|
|
3502
4894
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Card, { variant: "elevated", className: "rounded-xl", "data-testid": `${testIdPrefix}-agents-agents`, children: [
|
|
3503
4895
|
/* @__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-
|
|
4896
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bot, { className: "size-4 text-primary" }),
|
|
3505
4897
|
" Agents",
|
|
3506
4898
|
/* @__PURE__ */ jsxRuntime.jsx(react.Badge, { variant: "secondary", className: "ml-1", children: agents.length })
|
|
3507
4899
|
] }) }),
|
|
@@ -3523,7 +4915,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
|
|
|
3523
4915
|
}
|
|
3524
4916
|
),
|
|
3525
4917
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3526
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-
|
|
4918
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: (a.given_name ?? "Agent").replace(/-/g, " ") }),
|
|
3527
4919
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: a.external_id })
|
|
3528
4920
|
] })
|
|
3529
4921
|
]
|
|
@@ -3548,7 +4940,7 @@ function ExportPanel({
|
|
|
3548
4940
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Card, { variant: "elevated", className: "rounded-xl", "data-testid": `${testIdPrefix}-export-panel`, children: [
|
|
3549
4941
|
/* @__PURE__ */ jsxRuntime.jsx(react.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.CardTitle, { className: "flex items-center justify-between text-base", children: [
|
|
3550
4942
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
3551
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileJson, { className: "size-4 text-
|
|
4943
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileJson, { className: "size-4 text-primary" }),
|
|
3552
4944
|
" Vertical config"
|
|
3553
4945
|
] }),
|
|
3554
4946
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -3568,7 +4960,7 @@ function ExportPanel({
|
|
|
3568
4960
|
/* @__PURE__ */ jsxRuntime.jsx(react.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3569
4961
|
"pre",
|
|
3570
4962
|
{
|
|
3571
|
-
className: "scrollbar-slim max-h-[520px] overflow-auto rounded-md bg-
|
|
4963
|
+
className: "scrollbar-slim max-h-[520px] overflow-auto rounded-md bg-foreground p-4 font-mono text-[11px] leading-relaxed text-background",
|
|
3572
4964
|
"data-testid": `${testIdPrefix}-export-preview`,
|
|
3573
4965
|
children: json
|
|
3574
4966
|
}
|
|
@@ -3666,13 +5058,18 @@ Object.defineProperty(exports, "toRedlineMarkdown", {
|
|
|
3666
5058
|
enumerable: true,
|
|
3667
5059
|
get: function () { return react.toRedlineMarkdown; }
|
|
3668
5060
|
});
|
|
5061
|
+
exports.ASSET_REF_PREFIX = ASSET_REF_PREFIX;
|
|
5062
|
+
exports.Accordion = Accordion;
|
|
3669
5063
|
exports.AgentsPanel = AgentsPanel;
|
|
5064
|
+
exports.AssetResolverProvider = AssetResolverProvider;
|
|
3670
5065
|
exports.AvatarBlock = AvatarBlock;
|
|
5066
|
+
exports.Banner = Banner;
|
|
3671
5067
|
exports.BlockImage = BlockImage;
|
|
3672
5068
|
exports.CTABanner = CTABanner;
|
|
3673
5069
|
exports.Callout = Callout;
|
|
3674
5070
|
exports.Columns = Columns;
|
|
3675
5071
|
exports.ConnectionProvider = ConnectionProvider;
|
|
5072
|
+
exports.ContactForm = ContactForm;
|
|
3676
5073
|
exports.Container = Container;
|
|
3677
5074
|
exports.DataTable = DataTable;
|
|
3678
5075
|
exports.DataTableBlock = DataTableBlock;
|
|
@@ -3680,37 +5077,50 @@ exports.EmptyState = EmptyState;
|
|
|
3680
5077
|
exports.FAQ = FAQ;
|
|
3681
5078
|
exports.FeatureGrid = FeatureGrid;
|
|
3682
5079
|
exports.Footer = Footer;
|
|
5080
|
+
exports.Gallery = Gallery;
|
|
3683
5081
|
exports.GridView = GridView;
|
|
3684
5082
|
exports.Hero = Hero;
|
|
3685
5083
|
exports.LegalArbiProvider = LegalArbiProvider;
|
|
3686
5084
|
exports.ListBlock = ListBlock;
|
|
3687
5085
|
exports.LogoCloud = LogoCloud;
|
|
5086
|
+
exports.MediaText = MediaText;
|
|
3688
5087
|
exports.MetricCard = MetricCard;
|
|
3689
5088
|
exports.ModuleManager = ModuleManager;
|
|
3690
5089
|
exports.Navbar = Navbar;
|
|
3691
5090
|
exports.PageHeader = PageHeader;
|
|
3692
5091
|
exports.PageRenderer = PageRenderer;
|
|
5092
|
+
exports.PricingTable = PricingTable;
|
|
3693
5093
|
exports.RichTextBlock = RichTextBlock;
|
|
3694
5094
|
exports.Section = Section;
|
|
3695
5095
|
exports.SectionHeading = SectionHeading;
|
|
3696
5096
|
exports.Spacer = Spacer;
|
|
3697
5097
|
exports.StatGroup = StatGroup;
|
|
5098
|
+
exports.Steps = Steps;
|
|
3698
5099
|
exports.StudioEditor = StudioEditor;
|
|
3699
5100
|
exports.THEME_STYLE_ID = THEME_STYLE_ID;
|
|
5101
|
+
exports.Tabs = Tabs;
|
|
5102
|
+
exports.TeamGrid = TeamGrid;
|
|
3700
5103
|
exports.Testimonial = Testimonial;
|
|
5104
|
+
exports.TestimonialGrid = TestimonialGrid;
|
|
3701
5105
|
exports.ThemeEditor = ThemeEditor;
|
|
3702
5106
|
exports.Toolbar = Toolbar;
|
|
3703
5107
|
exports.VerticalBuilder = VerticalBuilder;
|
|
5108
|
+
exports.VideoEmbed = VideoEmbed;
|
|
3704
5109
|
exports.applyStoredTheme = applyStoredTheme;
|
|
3705
5110
|
exports.applyThemeVars = applyThemeVars;
|
|
5111
|
+
exports.asAssetRef = asAssetRef;
|
|
5112
|
+
exports.assetRefId = assetRefId;
|
|
3706
5113
|
exports.clearThemeVars = clearThemeVars;
|
|
3707
5114
|
exports.createAgentSelectionStore = createAgentSelectionStore;
|
|
3708
5115
|
exports.createAiFields = createAiFields;
|
|
3709
5116
|
exports.createArbiBlocksConfig = createArbiBlocksConfig;
|
|
5117
|
+
exports.createArbiMaterializer = createArbiMaterializer;
|
|
3710
5118
|
exports.createBlockKit = createBlockKit;
|
|
5119
|
+
exports.createImageField = createImageField;
|
|
3711
5120
|
exports.createModuleRegistry = createModuleRegistry;
|
|
3712
5121
|
exports.createModuleStore = createModuleStore;
|
|
3713
5122
|
exports.createPromptLibrary = createPromptLibrary;
|
|
5123
|
+
exports.createSiteBlocks = createSiteBlocks;
|
|
3714
5124
|
exports.createStudioPersistence = createStudioPersistence;
|
|
3715
5125
|
exports.createThemeStore = createThemeStore;
|
|
3716
5126
|
exports.createVerticalExport = createVerticalExport;
|
|
@@ -3723,13 +5133,18 @@ exports.gbp = gbp;
|
|
|
3723
5133
|
exports.hrs = hrs;
|
|
3724
5134
|
exports.iconMap = iconMap;
|
|
3725
5135
|
exports.initials = initials;
|
|
5136
|
+
exports.isAssetRef = isAssetRef;
|
|
3726
5137
|
exports.isAuthenticated = isAuthenticated;
|
|
5138
|
+
exports.materializePageData = materializePageData;
|
|
3727
5139
|
exports.matterToContext = matterToContext;
|
|
3728
5140
|
exports.pct = pct;
|
|
3729
5141
|
exports.tid = tid;
|
|
5142
|
+
exports.toEmbedUrl = toEmbedUrl;
|
|
3730
5143
|
exports.toHslTriplet = toHslTriplet;
|
|
5144
|
+
exports.useAssetResolverActive = useAssetResolverActive;
|
|
3731
5145
|
exports.useConnection = useConnection;
|
|
3732
5146
|
exports.useFirmAiTask = useFirmAiTask;
|
|
5147
|
+
exports.useImageGen = useImageGen;
|
|
3733
5148
|
exports.useSemanticSearch = useSemanticSearch;
|
|
3734
5149
|
exports.useWorkspaceDocs = useWorkspaceDocs;
|
|
3735
5150
|
//# sourceMappingURL=index.cjs.map
|