@veluai/velu 0.2.36 → 0.2.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/cli.js +122 -55
- package/docs/mintlify-migration.md +159 -0
- package/docs/vepa.md +97 -0
- package/package.json +8 -2
- package/runtime/velu-ui/components/ApiReferencePage.jsx +11 -3
- package/runtime/velu-ui/components/ApiSamples.jsx +9 -4
- package/runtime/velu-ui/components/Card.jsx +7 -4
- package/runtime/velu-ui/components/Chatbot.jsx +9 -4
- package/runtime/velu-ui/components/Columns.jsx +1 -0
- package/runtime/velu-ui/components/PageFooter.jsx +3 -0
- package/runtime/velu-ui/components/PageHeader.jsx +14 -7
- package/runtime/velu-ui/components/ThemePreferenceMenu.jsx +57 -0
- package/runtime/velu-ui/components/ThemeToggle.jsx +3 -1
- package/runtime/velu-ui/components/Update.jsx +22 -10
- package/runtime/velu-ui/components/VepaFooter.jsx +40 -0
- package/runtime/velu-ui/components/card.css +9 -0
- package/runtime/velu-ui/components/chatbot.css +42 -0
- package/runtime/velu-ui/components/docs-layout.css +75 -0
- package/runtime/velu-ui/components/page-header.css +6 -0
- package/runtime/velu-ui/styles.css +1 -0
- package/runtime/velu-ui/themes/vepa.css +396 -0
- package/schema/velu.schema.json +5 -0
- package/src/navigation.js +3 -2
- package/src/runtime/App.jsx +120 -50
- package/src/runtime/VepaToc.jsx +12 -0
- package/src/runtime/page-mode.js +54 -0
- package/templates/starter/essentials/settings.mdx +15 -0
package/src/runtime/App.jsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import VepaToc from './VepaToc.jsx';
|
|
2
3
|
import { Routes, Route, useLocation, useNavigate, Link } from 'react-router-dom';
|
|
3
4
|
import { MDXProvider } from '@mdx-js/react';
|
|
4
5
|
// The project's pages + navigation, generated from velu.json by
|
|
@@ -6,6 +7,7 @@ import { MDXProvider } from '@mdx-js/react';
|
|
|
6
7
|
// url → { Component, frontmatter, toc } (or { missing:true }).
|
|
7
8
|
import { pages, navigation, site } from 'virtual:velu-site';
|
|
8
9
|
import { resolve, normalizeUrl } from '../navigation.js';
|
|
10
|
+
import { resolvePageMode, pageModeChrome } from './page-mode.js';
|
|
9
11
|
import {
|
|
10
12
|
Stack,
|
|
11
13
|
Cluster,
|
|
@@ -53,6 +55,7 @@ import {
|
|
|
53
55
|
ApiField,
|
|
54
56
|
ApiSidebar,
|
|
55
57
|
ApiSamples,
|
|
58
|
+
ApiReferencePage,
|
|
56
59
|
NotFound,
|
|
57
60
|
VeluMark,
|
|
58
61
|
} from 'velu-ui';
|
|
@@ -691,7 +694,7 @@ const RouterLink = ({ href, ...rest }) => <Link to={href} {...rest} />;
|
|
|
691
694
|
// place.
|
|
692
695
|
function sectionById(id) {
|
|
693
696
|
if (typeof document === 'undefined' || !id) return null;
|
|
694
|
-
const article = document.
|
|
697
|
+
const article = document.querySelector('.velu-docs-layout__article');
|
|
695
698
|
const esc = id.replace(/["\\]/g, '\\$&');
|
|
696
699
|
return article?.querySelector(`[id="${esc}"]`) ?? document.getElementById(id);
|
|
697
700
|
}
|
|
@@ -855,6 +858,10 @@ function DocsPage() {
|
|
|
855
858
|
// No renderable page for this route → show the 404 page (a clean centered
|
|
856
859
|
// takeover: header + footer stay, the docs sidebar/TOC are hidden).
|
|
857
860
|
const isNotFound = !PageComponent;
|
|
861
|
+
// Mintlify-compatible page layout (`mode` in MDX frontmatter). Unknown
|
|
862
|
+
// values fall back to the standard sidebar + TOC + footer chrome.
|
|
863
|
+
const chrome = pageModeChrome(resolvePageMode(frontmatter));
|
|
864
|
+
const assistantUnavailable = chrome.assistant && !IS_DEV_PREVIEW && (!assistant || askAiHidden);
|
|
858
865
|
|
|
859
866
|
// Switcher option sets (only render a switcher when an axis has >1
|
|
860
867
|
// option). Anchors are pinned sidebar links shown in the context zone.
|
|
@@ -921,8 +928,9 @@ function DocsPage() {
|
|
|
921
928
|
// against it like any other heading.
|
|
922
929
|
const pageId = React.useMemo(() => {
|
|
923
930
|
if (!frontmatter.title) return null;
|
|
931
|
+
if (site.theme === 'vepa') return 'page-title';
|
|
924
932
|
return new GithubSlugger().slug(frontmatter.title);
|
|
925
|
-
}, [frontmatter.title]);
|
|
933
|
+
}, [frontmatter.title, site.theme]);
|
|
926
934
|
|
|
927
935
|
// Combined TOC: the frontmatter title is the page's top-level entry,
|
|
928
936
|
// and the MDX-derived headings (h2s) become its children. Without this,
|
|
@@ -1058,7 +1066,11 @@ function DocsPage() {
|
|
|
1058
1066
|
const tocBarToggle = document.querySelector('.velu-toc-bar__toggle');
|
|
1059
1067
|
if (tocBarToggle) ro.observe(tocBarToggle);
|
|
1060
1068
|
return () => ro.disconnect();
|
|
1061
|
-
|
|
1069
|
+
// Re-run when page mode changes: custom/assistant drop the tabs
|
|
1070
|
+
// row and shrink the header. A mount-only observer can leave
|
|
1071
|
+
// --velu-header-height at the two-row 7rem default, which leaves
|
|
1072
|
+
// a gap above the full-page Ask AI canvas.
|
|
1073
|
+
}, [chrome.mode]);
|
|
1062
1074
|
|
|
1063
1075
|
// The asides (left sidebar + right TOC) scroll independently of the
|
|
1064
1076
|
// page. On scroll/resize we set data-fade-top/-bottom on each scroll
|
|
@@ -1183,14 +1195,22 @@ function DocsPage() {
|
|
|
1183
1195
|
});
|
|
1184
1196
|
}
|
|
1185
1197
|
|
|
1186
|
-
|
|
1198
|
+
// Callback ref so this rebinds when the footer mounts/unmounts.
|
|
1199
|
+
// Custom and assistant unmount the footer; a mount-once listener
|
|
1200
|
+
// would keep reading the detached node (getBoundingClientRect top
|
|
1201
|
+
// is 0) and treat the footer as covering the full viewport, which
|
|
1202
|
+
// crushes the fixed sidebar via --velu-aside-bottom.
|
|
1203
|
+
const [footerEl, setFooterEl] = React.useState(null);
|
|
1187
1204
|
const [footerOverlap, setFooterOverlap] = React.useState(0);
|
|
1188
1205
|
React.useEffect(() => {
|
|
1189
|
-
|
|
1190
|
-
|
|
1206
|
+
if (!footerEl) {
|
|
1207
|
+
setFooterOverlap(0);
|
|
1208
|
+
return undefined;
|
|
1209
|
+
}
|
|
1191
1210
|
const update = () => {
|
|
1192
|
-
const rect =
|
|
1193
|
-
|
|
1211
|
+
const rect = footerEl.getBoundingClientRect();
|
|
1212
|
+
const visible = Math.max(0, window.innerHeight - rect.top);
|
|
1213
|
+
setFooterOverlap(Math.min(visible, footerEl.offsetHeight));
|
|
1194
1214
|
};
|
|
1195
1215
|
update();
|
|
1196
1216
|
window.addEventListener('scroll', update, { passive: true });
|
|
@@ -1199,15 +1219,17 @@ function DocsPage() {
|
|
|
1199
1219
|
window.removeEventListener('scroll', update);
|
|
1200
1220
|
window.removeEventListener('resize', update);
|
|
1201
1221
|
};
|
|
1202
|
-
}, []);
|
|
1222
|
+
}, [footerEl]);
|
|
1203
1223
|
|
|
1204
1224
|
// Sticky AskBar fades out once the user scrolls near the PageFeedback
|
|
1205
1225
|
// widget — so it doesn't sit on top of the page-foot widgets.
|
|
1206
|
-
const
|
|
1226
|
+
const [feedbackEl, setFeedbackEl] = React.useState(null);
|
|
1207
1227
|
const [askBarHidden, setAskBarHidden] = React.useState(false);
|
|
1208
1228
|
React.useEffect(() => {
|
|
1209
|
-
|
|
1210
|
-
|
|
1229
|
+
if (!feedbackEl) {
|
|
1230
|
+
setAskBarHidden(false);
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1211
1233
|
const FADE_BUFFER = 64; // matches the rootMargin below
|
|
1212
1234
|
const apply = (rect) => {
|
|
1213
1235
|
// Hide whenever the feedback's top has reached the (effective)
|
|
@@ -1217,16 +1239,16 @@ function DocsPage() {
|
|
|
1217
1239
|
// exactly at the same line in both scroll directions.
|
|
1218
1240
|
setAskBarHidden(rect.top < window.innerHeight - FADE_BUFFER);
|
|
1219
1241
|
};
|
|
1220
|
-
apply(
|
|
1242
|
+
apply(feedbackEl.getBoundingClientRect());
|
|
1221
1243
|
const io = new IntersectionObserver(
|
|
1222
1244
|
([entry]) => apply(entry.boundingClientRect),
|
|
1223
1245
|
// Trigger ~64px before the feedback enters the viewport, so the
|
|
1224
1246
|
// AskBar fades just as the widget starts to peek up from below.
|
|
1225
1247
|
{ rootMargin: '0px 0px -64px 0px' },
|
|
1226
1248
|
);
|
|
1227
|
-
io.observe(
|
|
1249
|
+
io.observe(feedbackEl);
|
|
1228
1250
|
return () => io.disconnect();
|
|
1229
|
-
}, []);
|
|
1251
|
+
}, [feedbackEl]);
|
|
1230
1252
|
|
|
1231
1253
|
const scrollTo = React.useCallback(
|
|
1232
1254
|
(id) => {
|
|
@@ -1263,11 +1285,13 @@ function DocsPage() {
|
|
|
1263
1285
|
<div
|
|
1264
1286
|
id="body-content"
|
|
1265
1287
|
className="velu-docs-layout"
|
|
1266
|
-
data-
|
|
1288
|
+
data-site-theme={site.theme ?? 'velu'}
|
|
1289
|
+
data-chat-open={chatOpen && !chrome.assistant ? 'true' : 'false'}
|
|
1267
1290
|
data-sidebar-open={sidebarOpen ? 'true' : 'false'}
|
|
1268
1291
|
data-drawer-open={drawerOpen ? 'true' : 'false'}
|
|
1269
1292
|
data-api={entry?.api ? 'true' : 'false'}
|
|
1270
1293
|
data-not-found={isNotFound ? 'true' : undefined}
|
|
1294
|
+
data-page-mode={isNotFound ? undefined : chrome.mode}
|
|
1271
1295
|
>
|
|
1272
1296
|
{/* Scrim — visible at mobile while the drawer OR the chatbot
|
|
1273
1297
|
sheet is open. Sits between the article (z-0) and the
|
|
@@ -1284,6 +1308,7 @@ function DocsPage() {
|
|
|
1284
1308
|
{/* Site header — brand, centered search, right-side actions,
|
|
1285
1309
|
tabs row. Configurable: pass any number of actions / tabs. */}
|
|
1286
1310
|
<PageHeader
|
|
1311
|
+
preset={site.theme}
|
|
1287
1312
|
id="navbar"
|
|
1288
1313
|
linkComponent={RouterLink}
|
|
1289
1314
|
brand={{
|
|
@@ -1296,7 +1321,6 @@ function DocsPage() {
|
|
|
1296
1321
|
<span className="velu-hide-on-mobile">{versionSwitcher}</span>
|
|
1297
1322
|
)
|
|
1298
1323
|
}
|
|
1299
|
-
tabsTrailing={languageSwitcher || undefined}
|
|
1300
1324
|
center={
|
|
1301
1325
|
<Cluster space="var(--s-6)" align="center">
|
|
1302
1326
|
<Search
|
|
@@ -1309,7 +1333,7 @@ function DocsPage() {
|
|
|
1309
1333
|
{/* Ask AI talks to the deployed site's AI backend — hidden in
|
|
1310
1334
|
the local dev preview where there's nothing to talk to, and
|
|
1311
1335
|
when the owner's plan/credits disable the assistant. */}
|
|
1312
|
-
{!IS_DEV_PREVIEW && !askAiHidden && (
|
|
1336
|
+
{!IS_DEV_PREVIEW && !askAiHidden && !chrome.assistant && (
|
|
1313
1337
|
<button
|
|
1314
1338
|
type="button"
|
|
1315
1339
|
id="assistant-entry"
|
|
@@ -1325,11 +1349,24 @@ function DocsPage() {
|
|
|
1325
1349
|
</Cluster>
|
|
1326
1350
|
}
|
|
1327
1351
|
actions={navActions}
|
|
1328
|
-
trailing={<ThemeToggle />}
|
|
1329
|
-
|
|
1330
|
-
|
|
1352
|
+
trailing={<ThemeToggle variant={site.theme === 'vepa' ? 'menu' : undefined} />}
|
|
1353
|
+
tabsTrailing={
|
|
1354
|
+
chrome.mode === 'custom' || chrome.mode === 'assistant'
|
|
1355
|
+
? undefined
|
|
1356
|
+
: languageSwitcher || undefined
|
|
1357
|
+
}
|
|
1358
|
+
onMenuClick={chrome.sidebar ? () => setDrawerOpen((v) => !v) : undefined}
|
|
1359
|
+
breadcrumb={
|
|
1360
|
+
chrome.mode === 'custom' || chrome.mode === 'assistant'
|
|
1361
|
+
? []
|
|
1362
|
+
: (nav?.breadcrumb ?? [])
|
|
1363
|
+
}
|
|
1331
1364
|
activeTab={nav?.activeTab}
|
|
1332
|
-
tabs={
|
|
1365
|
+
tabs={
|
|
1366
|
+
chrome.mode === 'custom' || chrome.mode === 'assistant'
|
|
1367
|
+
? []
|
|
1368
|
+
: (nav?.tabs ?? [])
|
|
1369
|
+
}
|
|
1333
1370
|
/>
|
|
1334
1371
|
|
|
1335
1372
|
{/* Fixed left sidebar — pinned to viewport-left below the header.
|
|
@@ -1339,7 +1376,7 @@ function DocsPage() {
|
|
|
1339
1376
|
honour `inset-block-start` for fixed positioning the same way
|
|
1340
1377
|
as plain `top`). */}
|
|
1341
1378
|
<aside
|
|
1342
|
-
id=
|
|
1379
|
+
id={site.theme === 'vepa' ? 'sidebar-content' : 'sidebar'}
|
|
1343
1380
|
className="velu-docs-layout__aside velu-docs-layout__aside--left"
|
|
1344
1381
|
style={{
|
|
1345
1382
|
/* Bottom edge stays a fixed gap above the viewport bottom,
|
|
@@ -1380,7 +1417,7 @@ function DocsPage() {
|
|
|
1380
1417
|
</>
|
|
1381
1418
|
)}
|
|
1382
1419
|
</RouterLink>
|
|
1383
|
-
<ThemeToggle />
|
|
1420
|
+
<ThemeToggle variant={site.theme === 'vepa' ? 'menu' : undefined} />
|
|
1384
1421
|
<button
|
|
1385
1422
|
type="button"
|
|
1386
1423
|
className="velu-docs-layout__drawer-close"
|
|
@@ -1393,7 +1430,9 @@ function DocsPage() {
|
|
|
1393
1430
|
{/* Nav dropdown — custom button + menu, fills drawer width.
|
|
1394
1431
|
Items mirror the desktop tabs (Home / Docs / Blog).
|
|
1395
1432
|
Click-outside + Escape close (see navOpen useEffect
|
|
1396
|
-
above). Chevron rotates 180° on open.
|
|
1433
|
+
above). Chevron rotates 180° on open. Shown only when
|
|
1434
|
+
there is more than one tab to switch between. */}
|
|
1435
|
+
{navItems.length > 1 && (
|
|
1397
1436
|
<div
|
|
1398
1437
|
ref={navRef}
|
|
1399
1438
|
className="velu-docs-layout__drawer-docselect"
|
|
@@ -1437,6 +1476,7 @@ function DocsPage() {
|
|
|
1437
1476
|
))}
|
|
1438
1477
|
</ul>
|
|
1439
1478
|
</div>
|
|
1479
|
+
)}
|
|
1440
1480
|
{/* Context zone — product switcher + anchors at the top of
|
|
1441
1481
|
the sidebar (all breakpoints), plus version/language
|
|
1442
1482
|
switchers that only show on mobile (desktop has them in
|
|
@@ -1493,7 +1533,7 @@ function DocsPage() {
|
|
|
1493
1533
|
nav fully to that end. */}
|
|
1494
1534
|
<div className="velu-docs-nav-region">
|
|
1495
1535
|
<div
|
|
1496
|
-
id=
|
|
1536
|
+
id={site.theme === 'vepa' ? 'navigation-items' : 'sidebar-content'}
|
|
1497
1537
|
ref={leftAsideRef}
|
|
1498
1538
|
className="velu-docs-nav-scroll velu-hide-scrollbar"
|
|
1499
1539
|
style={{
|
|
@@ -1561,8 +1601,9 @@ function DocsPage() {
|
|
|
1561
1601
|
display:none snap. See docs-layout.css. */}
|
|
1562
1602
|
|
|
1563
1603
|
{/* Fixed right TOC — pinned to viewport-right. Hidden while the
|
|
1564
|
-
Ask-AI chatbot is open (the panel takes that edge).
|
|
1565
|
-
|
|
1604
|
+
Ask-AI chatbot is open (the panel takes that edge). Wide /
|
|
1605
|
+
custom / frame / center / assistant hide this rail. */}
|
|
1606
|
+
{!chatOpen && chrome.toc && (
|
|
1566
1607
|
<aside
|
|
1567
1608
|
id="table-of-contents"
|
|
1568
1609
|
ref={rightAsideRef}
|
|
@@ -1578,11 +1619,20 @@ function DocsPage() {
|
|
|
1578
1619
|
(where the TOC sits for normal pages). */}
|
|
1579
1620
|
{entry?.api && entry.operation ? (
|
|
1580
1621
|
<ApiSamples
|
|
1622
|
+
preset={site.theme}
|
|
1623
|
+
title={frontmatter.title}
|
|
1581
1624
|
samples={entry.samples}
|
|
1582
1625
|
responses={entry.operation.responses}
|
|
1583
1626
|
/>
|
|
1584
|
-
) : isChangelog && changelogTags.length ? (
|
|
1627
|
+
) : isChangelog && changelogTags.length && site.theme !== 'vepa' ? (
|
|
1585
1628
|
<ChangelogFilters tags={changelogTags} />
|
|
1629
|
+
) : site.theme === 'vepa' ? (
|
|
1630
|
+
<>
|
|
1631
|
+
{isChangelog && changelogTags.length > 0 ? (
|
|
1632
|
+
<ChangelogFilters tags={changelogTags} />
|
|
1633
|
+
) : null}
|
|
1634
|
+
<VepaToc items={pageToc} activeId={activeId} onSelect={scrollTo} />
|
|
1635
|
+
</>
|
|
1586
1636
|
) : (
|
|
1587
1637
|
<Toc id="table-of-contents-content" items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1588
1638
|
)}
|
|
@@ -1607,7 +1657,14 @@ function DocsPage() {
|
|
|
1607
1657
|
onAskAI={IS_DEV_PREVIEW || askAiHidden ? undefined : () => askAI('')}
|
|
1608
1658
|
/>
|
|
1609
1659
|
</main>
|
|
1610
|
-
) : (
|
|
1660
|
+
) : chrome.assistant && assistantUnavailable ? (
|
|
1661
|
+
<main className="velu-assistant-disabled">
|
|
1662
|
+
<Search
|
|
1663
|
+
unavailable
|
|
1664
|
+
unavailableMessage="Ask AI is not enabled on this site."
|
|
1665
|
+
/>
|
|
1666
|
+
</main>
|
|
1667
|
+
) : chrome.assistant ? null : (
|
|
1611
1668
|
<>
|
|
1612
1669
|
{/* Narrow-layout TOC bar — always in DOM, only visible at
|
|
1613
1670
|
< 1024px (toggled by @container in toc-bar.css). Shares
|
|
@@ -1616,8 +1673,11 @@ function DocsPage() {
|
|
|
1616
1673
|
data; whichever one is visible at a given width responds
|
|
1617
1674
|
to the same scroll-spy state. */}
|
|
1618
1675
|
{/* Changelog tag filters are a wide-layout (right-rail) affordance only;
|
|
1619
|
-
on tab/mobile we keep the normal TOC bar (no filters).
|
|
1676
|
+
on tab/mobile we keep the normal TOC bar (no filters). Hidden
|
|
1677
|
+
entirely when the page mode has no table of contents. */}
|
|
1678
|
+
{chrome.toc && (
|
|
1620
1679
|
<TocBar items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1680
|
+
)}
|
|
1621
1681
|
<main
|
|
1622
1682
|
id="content-area"
|
|
1623
1683
|
className="velu-docs-layout__main"
|
|
@@ -1641,6 +1701,7 @@ function DocsPage() {
|
|
|
1641
1701
|
column, and therefore sits BELOW the TocBar rather than
|
|
1642
1702
|
overlapping it. Chevron flips direction with the
|
|
1643
1703
|
`data-sidebar-open` data attribute on the layout root. */}
|
|
1704
|
+
{chrome.sidebar && (
|
|
1644
1705
|
<button
|
|
1645
1706
|
type="button"
|
|
1646
1707
|
className="velu-docs-layout__sidebar-toggle"
|
|
@@ -1654,11 +1715,13 @@ function DocsPage() {
|
|
|
1654
1715
|
})}
|
|
1655
1716
|
</span>
|
|
1656
1717
|
</button>
|
|
1657
|
-
|
|
1718
|
+
)}
|
|
1719
|
+
<div id={site.theme === 'vepa' ? undefined : 'content'} data-contextual-actions={site.contextual?.options?.length ? 'true' : 'false'} className="velu-docs-layout__article" data-pagefind-body={chrome.assistant ? undefined : ""}>
|
|
1658
1720
|
{/* Per-page agent/IDE action bar: the section eyebrow + a
|
|
1659
1721
|
"Copy Page" split-button whose dropdown is driven by the
|
|
1660
1722
|
Mintlify-compatible `contextual` config. Renders nothing
|
|
1661
1723
|
when there's neither an eyebrow nor any enabled options. */}
|
|
1724
|
+
{chrome.articleChrome && (
|
|
1662
1725
|
<ContextMenu
|
|
1663
1726
|
eyebrow={
|
|
1664
1727
|
nav?.breadcrumb && nav.breadcrumb.length > 1
|
|
@@ -1675,13 +1738,15 @@ function DocsPage() {
|
|
|
1675
1738
|
getMarkdown={entry?.getSource}
|
|
1676
1739
|
rssHref={isChangelog ? `${BASE_PATH}/rss.xml` : undefined}
|
|
1677
1740
|
/>
|
|
1741
|
+
)}
|
|
1678
1742
|
|
|
1679
1743
|
{/* Page hero from MDX frontmatter. `.velu-hero` rules
|
|
1680
1744
|
(in base.css) keep the title and description tightly
|
|
1681
|
-
grouped and create a clear break before the prose body.
|
|
1682
|
-
|
|
1745
|
+
grouped and create a clear break before the prose body.
|
|
1746
|
+
Custom / frame / assistant are a blank canvas — no auto hero. */}
|
|
1747
|
+
{chrome.hero && (frontmatter.title || frontmatter.description) && (
|
|
1683
1748
|
<div className="velu-hero">
|
|
1684
|
-
{frontmatter.title && <h1 id={pageId}>{frontmatter.title}</h1>}
|
|
1749
|
+
{frontmatter.title && <h1 id={site.theme === 'vepa' ? 'page-title' : pageId}>{frontmatter.title}</h1>}
|
|
1685
1750
|
{frontmatter.description && (
|
|
1686
1751
|
<p
|
|
1687
1752
|
style={{
|
|
@@ -1702,10 +1767,10 @@ function DocsPage() {
|
|
|
1702
1767
|
level. Falls back to a not-found / missing-file notice
|
|
1703
1768
|
when the route has no page (or its file is absent). */}
|
|
1704
1769
|
<MDXProvider components={defaultMdxComponents}>
|
|
1705
|
-
<mdx-content class="velu-prose">
|
|
1770
|
+
<mdx-content key={site.theme === 'vepa' ? pathname : undefined} id={site.theme === 'vepa' && !entry?.api ? 'content' : undefined} class="velu-prose">
|
|
1706
1771
|
{PageComponent ? (
|
|
1707
1772
|
<ErrorBoundary key={pathname} file={entry?.relPath}>
|
|
1708
|
-
<PageComponent />
|
|
1773
|
+
{entry?.mdxApi ? <ApiReferencePage {...entry.mdxApi} preset={site.theme}><PageComponent /></ApiReferencePage> : <PageComponent />}
|
|
1709
1774
|
</ErrorBoundary>
|
|
1710
1775
|
) : (
|
|
1711
1776
|
<p style={{ color: 'var(--muted-color)' }}>
|
|
@@ -1718,7 +1783,8 @@ function DocsPage() {
|
|
|
1718
1783
|
</MDXProvider>
|
|
1719
1784
|
{/* Page-foot feedback widget — ref'd so the sticky AskBar
|
|
1720
1785
|
above can hide as the user scrolls near it. */}
|
|
1721
|
-
|
|
1786
|
+
{chrome.articleChrome && (
|
|
1787
|
+
<div ref={setFeedbackEl} style={{ marginTop: 'var(--s3)' }}>
|
|
1722
1788
|
{/* key resets the widget's vote state on page change. "Yes"
|
|
1723
1789
|
submits immediately; "No" submits once its form is sent.
|
|
1724
1790
|
Handlers are no-ops in the dev preview (pageFeedback null). */}
|
|
@@ -1732,9 +1798,10 @@ function DocsPage() {
|
|
|
1732
1798
|
}
|
|
1733
1799
|
/>
|
|
1734
1800
|
</div>
|
|
1801
|
+
)}
|
|
1735
1802
|
{/* Previous / next page navigation — derived from the
|
|
1736
1803
|
sidebar reading order of the active section. */}
|
|
1737
|
-
{(nav?.prev || nav?.next) && (
|
|
1804
|
+
{chrome.articleChrome && !frontmatter.hidePagination && (nav?.prev || nav?.next) && (
|
|
1738
1805
|
<PageNav
|
|
1739
1806
|
id="pagination"
|
|
1740
1807
|
style={{ marginTop: 'var(--s2)' }}
|
|
@@ -1750,7 +1817,7 @@ function DocsPage() {
|
|
|
1750
1817
|
approaches (see IntersectionObserver above). */}
|
|
1751
1818
|
{/* Ask-a-question bar — opens the AI chatbot, which runs on the
|
|
1752
1819
|
deployed site. Hidden in the local dev preview. */}
|
|
1753
|
-
{!IS_DEV_PREVIEW && !chatOpen && !askAiHidden && (
|
|
1820
|
+
{!IS_DEV_PREVIEW && !chatOpen && !askAiHidden && chrome.articleChrome && (
|
|
1754
1821
|
<AskBar
|
|
1755
1822
|
onSubmit={askAI}
|
|
1756
1823
|
style={{
|
|
@@ -1776,14 +1843,14 @@ function DocsPage() {
|
|
|
1776
1843
|
configured social icons sit on the LEFT of this same row (no
|
|
1777
1844
|
full footer section); with link columns they live in the full
|
|
1778
1845
|
footer below instead. */}
|
|
1779
|
-
{footerHasLinks ? (
|
|
1780
|
-
<PoweredBy />
|
|
1846
|
+
{chrome.articleChrome && (footerHasLinks ? (
|
|
1847
|
+
site.theme === 'vepa' ? null : <PoweredBy />
|
|
1781
1848
|
) : (
|
|
1782
1849
|
<div className="velu-content-foot">
|
|
1783
1850
|
<SocialLinks socials={footerSocials} />
|
|
1784
1851
|
<PoweredBy />
|
|
1785
1852
|
</div>
|
|
1786
|
-
)}
|
|
1853
|
+
))}
|
|
1787
1854
|
</div>
|
|
1788
1855
|
</main>
|
|
1789
1856
|
</>
|
|
@@ -1794,15 +1861,16 @@ function DocsPage() {
|
|
|
1794
1861
|
full width below the article; its raised z-index eclipses the bottoms
|
|
1795
1862
|
of the fixed sidebar and TOC as the page scrolls into it, and the ref
|
|
1796
1863
|
is watched so the asides can pad their bottom by the overlap. */}
|
|
1797
|
-
{footerHasLinks && (
|
|
1864
|
+
{footerHasLinks && chrome.footer && (
|
|
1798
1865
|
<advanced-footer
|
|
1799
|
-
id=
|
|
1800
|
-
ref={
|
|
1866
|
+
id={site.theme === 'vepa' ? undefined : 'footer'}
|
|
1867
|
+
ref={setFooterEl}
|
|
1801
1868
|
data-velu-footer
|
|
1802
1869
|
style={{ position: 'relative', zIndex: 20 }}
|
|
1803
1870
|
>
|
|
1804
1871
|
<PageFooter
|
|
1805
|
-
|
|
1872
|
+
preset={site.theme}
|
|
1873
|
+
brand={{ label: site.name, logo: site.logo, href: site.logo?.href || '/' }}
|
|
1806
1874
|
columns={site.footer.links}
|
|
1807
1875
|
socials={footerSocials}
|
|
1808
1876
|
/>
|
|
@@ -1811,11 +1879,12 @@ function DocsPage() {
|
|
|
1811
1879
|
|
|
1812
1880
|
{/* Ask-AI chatbot — slides in from the inline-end edge. Hidden when the
|
|
1813
1881
|
docs owner's plan/credits disable Ask-AI. */}
|
|
1814
|
-
{!askAiHidden && (
|
|
1882
|
+
{!askAiHidden && (!chrome.assistant || !assistantUnavailable) && (
|
|
1815
1883
|
<Chatbot
|
|
1816
|
-
open={chatOpen}
|
|
1884
|
+
open={chrome.assistant || chatOpen}
|
|
1885
|
+
layout={chrome.assistant ? 'page' : 'panel'}
|
|
1817
1886
|
seedQuestion={chatQuestion}
|
|
1818
|
-
onClose={() => setChatOpen(false)}
|
|
1887
|
+
onClose={chrome.assistant ? undefined : () => setChatOpen(false)}
|
|
1819
1888
|
ask={assistant?.ask}
|
|
1820
1889
|
onFeedback={assistant?.sendFeedback}
|
|
1821
1890
|
listHistory={assistant?.listConversations}
|
|
@@ -1838,6 +1907,7 @@ function DocsPage() {
|
|
|
1838
1907
|
// behind the sheet. Desktop keeps the panel open so multi-cite hops
|
|
1839
1908
|
// stay convenient.
|
|
1840
1909
|
if (
|
|
1910
|
+
!chrome.assistant &&
|
|
1841
1911
|
typeof window !== 'undefined' &&
|
|
1842
1912
|
window.matchMedia('(max-width: 1024px)').matches
|
|
1843
1913
|
) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function VepaToc({ items, activeId, onSelect }) {
|
|
4
|
+
const render = (entries) => <ul>{entries.map((item) => <li key={item.id}>
|
|
5
|
+
<a href={`#${item.id}`} aria-current={activeId === item.id ? 'location' : undefined} onClick={(e) => { e.preventDefault(); onSelect(item.id); }}>{item.label}</a>
|
|
6
|
+
{item.children?.length > 0 && render(item.children)}
|
|
7
|
+
</li>)}</ul>;
|
|
8
|
+
return <nav className="velu-vepa-toc" aria-label="On this page">
|
|
9
|
+
<button type="button" onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>On this page</button>
|
|
10
|
+
{render(items)}
|
|
11
|
+
</nav>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Page layout modes — Mintlify-compatible `mode` frontmatter.
|
|
2
|
+
// Unknown / missing values fall back to the standard docs chrome.
|
|
3
|
+
|
|
4
|
+
export const PAGE_MODES = [
|
|
5
|
+
'default',
|
|
6
|
+
'wide',
|
|
7
|
+
'custom',
|
|
8
|
+
'frame',
|
|
9
|
+
'center',
|
|
10
|
+
'assistant',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Record<string, unknown>} [frontmatter]
|
|
15
|
+
* @returns {'default' | 'wide' | 'custom' | 'frame' | 'center' | 'assistant'}
|
|
16
|
+
*/
|
|
17
|
+
export function resolvePageMode(frontmatter = {}) {
|
|
18
|
+
const raw = String(frontmatter.mode ?? 'default').trim().toLowerCase();
|
|
19
|
+
return PAGE_MODES.includes(raw) ? raw : 'default';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Which chrome pieces a mode shows. Used by the docs runtime; CSS also
|
|
24
|
+
* keys off `data-page-mode` for the corresponding layout (aside margins,
|
|
25
|
+
* article measure).
|
|
26
|
+
*
|
|
27
|
+
* default — sidebar + TOC + footer (standard docs page)
|
|
28
|
+
* wide — sidebar + footer; no TOC panel; keeps the right gutter
|
|
29
|
+
* custom — no sidebar/TOC/footer; centered landing canvas
|
|
30
|
+
* frame — custom canvas + sidebar (no TOC)
|
|
31
|
+
* center — no sidebar/TOC; centered reading (changelogs)
|
|
32
|
+
* assistant — navbar + full-page Ask AI chat
|
|
33
|
+
*
|
|
34
|
+
* @param {string} mode
|
|
35
|
+
*/
|
|
36
|
+
export function pageModeChrome(mode) {
|
|
37
|
+
const m = PAGE_MODES.includes(mode) ? mode : 'default';
|
|
38
|
+
const sidebar = m === 'default' || m === 'wide' || m === 'frame';
|
|
39
|
+
const toc = m === 'default';
|
|
40
|
+
const footer = m === 'default' || m === 'wide' || m === 'frame' || m === 'center';
|
|
41
|
+
const hero = m === 'default' || m === 'wide' || m === 'center';
|
|
42
|
+
return {
|
|
43
|
+
mode: m,
|
|
44
|
+
sidebar,
|
|
45
|
+
toc,
|
|
46
|
+
footer,
|
|
47
|
+
hero,
|
|
48
|
+
// Context menu, feedback, prev/next, AskBar, powered-by — the
|
|
49
|
+
// docs-page chrome around the MDX body. Custom/frame are a canvas;
|
|
50
|
+
// assistant replaces the body with chat.
|
|
51
|
+
articleChrome: hero,
|
|
52
|
+
assistant: m === 'assistant',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -28,3 +28,18 @@ applied as the body font.
|
|
|
28
28
|
`favicon` is a path relative to your project root, e.g.
|
|
29
29
|
`/favicon.svg`.
|
|
30
30
|
</Callout>
|
|
31
|
+
|
|
32
|
+
## Page layout
|
|
33
|
+
|
|
34
|
+
Set `mode` in a page's frontmatter to change its chrome. `default` is a
|
|
35
|
+
sidebar plus table of contents; `wide` drops the right panel; `custom` is
|
|
36
|
+
navbar-only; `frame` keeps the sidebar on a custom canvas; `center` is for
|
|
37
|
+
changelogs; `assistant` is a full-page Ask AI chat.
|
|
38
|
+
|
|
39
|
+
```mdx
|
|
40
|
+
---
|
|
41
|
+
title: "Changelog"
|
|
42
|
+
mode: "center"
|
|
43
|
+
---
|
|
44
|
+
```
|
|
45
|
+
|