@veluai/velu 0.2.36 → 0.2.37
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 +96 -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/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 +264 -0
- package/schema/velu.schema.json +5 -0
- package/src/navigation.js +3 -2
- package/src/runtime/App.jsx +108 -48
- 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,
|
|
@@ -1493,7 +1530,7 @@ function DocsPage() {
|
|
|
1493
1530
|
nav fully to that end. */}
|
|
1494
1531
|
<div className="velu-docs-nav-region">
|
|
1495
1532
|
<div
|
|
1496
|
-
id=
|
|
1533
|
+
id={site.theme === 'vepa' ? 'navigation-items' : 'sidebar-content'}
|
|
1497
1534
|
ref={leftAsideRef}
|
|
1498
1535
|
className="velu-docs-nav-scroll velu-hide-scrollbar"
|
|
1499
1536
|
style={{
|
|
@@ -1561,8 +1598,9 @@ function DocsPage() {
|
|
|
1561
1598
|
display:none snap. See docs-layout.css. */}
|
|
1562
1599
|
|
|
1563
1600
|
{/* Fixed right TOC — pinned to viewport-right. Hidden while the
|
|
1564
|
-
Ask-AI chatbot is open (the panel takes that edge).
|
|
1565
|
-
|
|
1601
|
+
Ask-AI chatbot is open (the panel takes that edge). Wide /
|
|
1602
|
+
custom / frame / center / assistant hide this rail. */}
|
|
1603
|
+
{!chatOpen && chrome.toc && (
|
|
1566
1604
|
<aside
|
|
1567
1605
|
id="table-of-contents"
|
|
1568
1606
|
ref={rightAsideRef}
|
|
@@ -1578,13 +1616,15 @@ function DocsPage() {
|
|
|
1578
1616
|
(where the TOC sits for normal pages). */}
|
|
1579
1617
|
{entry?.api && entry.operation ? (
|
|
1580
1618
|
<ApiSamples
|
|
1619
|
+
preset={site.theme}
|
|
1620
|
+
title={frontmatter.title}
|
|
1581
1621
|
samples={entry.samples}
|
|
1582
1622
|
responses={entry.operation.responses}
|
|
1583
1623
|
/>
|
|
1584
1624
|
) : isChangelog && changelogTags.length ? (
|
|
1585
1625
|
<ChangelogFilters tags={changelogTags} />
|
|
1586
1626
|
) : (
|
|
1587
|
-
<Toc id="table-of-contents-content" items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1627
|
+
site.theme === 'vepa' ? <VepaToc items={pageToc} activeId={activeId} onSelect={scrollTo} /> : <Toc id="table-of-contents-content" items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1588
1628
|
)}
|
|
1589
1629
|
</aside>
|
|
1590
1630
|
)}
|
|
@@ -1607,7 +1647,14 @@ function DocsPage() {
|
|
|
1607
1647
|
onAskAI={IS_DEV_PREVIEW || askAiHidden ? undefined : () => askAI('')}
|
|
1608
1648
|
/>
|
|
1609
1649
|
</main>
|
|
1610
|
-
) : (
|
|
1650
|
+
) : chrome.assistant && assistantUnavailable ? (
|
|
1651
|
+
<main className="velu-assistant-disabled">
|
|
1652
|
+
<Search
|
|
1653
|
+
unavailable
|
|
1654
|
+
unavailableMessage="Ask AI is not enabled on this site."
|
|
1655
|
+
/>
|
|
1656
|
+
</main>
|
|
1657
|
+
) : chrome.assistant ? null : (
|
|
1611
1658
|
<>
|
|
1612
1659
|
{/* Narrow-layout TOC bar — always in DOM, only visible at
|
|
1613
1660
|
< 1024px (toggled by @container in toc-bar.css). Shares
|
|
@@ -1616,8 +1663,11 @@ function DocsPage() {
|
|
|
1616
1663
|
data; whichever one is visible at a given width responds
|
|
1617
1664
|
to the same scroll-spy state. */}
|
|
1618
1665
|
{/* Changelog tag filters are a wide-layout (right-rail) affordance only;
|
|
1619
|
-
on tab/mobile we keep the normal TOC bar (no filters).
|
|
1666
|
+
on tab/mobile we keep the normal TOC bar (no filters). Hidden
|
|
1667
|
+
entirely when the page mode has no table of contents. */}
|
|
1668
|
+
{chrome.toc && (
|
|
1620
1669
|
<TocBar items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1670
|
+
)}
|
|
1621
1671
|
<main
|
|
1622
1672
|
id="content-area"
|
|
1623
1673
|
className="velu-docs-layout__main"
|
|
@@ -1641,6 +1691,7 @@ function DocsPage() {
|
|
|
1641
1691
|
column, and therefore sits BELOW the TocBar rather than
|
|
1642
1692
|
overlapping it. Chevron flips direction with the
|
|
1643
1693
|
`data-sidebar-open` data attribute on the layout root. */}
|
|
1694
|
+
{chrome.sidebar && (
|
|
1644
1695
|
<button
|
|
1645
1696
|
type="button"
|
|
1646
1697
|
className="velu-docs-layout__sidebar-toggle"
|
|
@@ -1654,11 +1705,13 @@ function DocsPage() {
|
|
|
1654
1705
|
})}
|
|
1655
1706
|
</span>
|
|
1656
1707
|
</button>
|
|
1657
|
-
|
|
1708
|
+
)}
|
|
1709
|
+
<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
1710
|
{/* Per-page agent/IDE action bar: the section eyebrow + a
|
|
1659
1711
|
"Copy Page" split-button whose dropdown is driven by the
|
|
1660
1712
|
Mintlify-compatible `contextual` config. Renders nothing
|
|
1661
1713
|
when there's neither an eyebrow nor any enabled options. */}
|
|
1714
|
+
{chrome.articleChrome && (
|
|
1662
1715
|
<ContextMenu
|
|
1663
1716
|
eyebrow={
|
|
1664
1717
|
nav?.breadcrumb && nav.breadcrumb.length > 1
|
|
@@ -1675,13 +1728,15 @@ function DocsPage() {
|
|
|
1675
1728
|
getMarkdown={entry?.getSource}
|
|
1676
1729
|
rssHref={isChangelog ? `${BASE_PATH}/rss.xml` : undefined}
|
|
1677
1730
|
/>
|
|
1731
|
+
)}
|
|
1678
1732
|
|
|
1679
1733
|
{/* Page hero from MDX frontmatter. `.velu-hero` rules
|
|
1680
1734
|
(in base.css) keep the title and description tightly
|
|
1681
|
-
grouped and create a clear break before the prose body.
|
|
1682
|
-
|
|
1735
|
+
grouped and create a clear break before the prose body.
|
|
1736
|
+
Custom / frame / assistant are a blank canvas — no auto hero. */}
|
|
1737
|
+
{chrome.hero && (frontmatter.title || frontmatter.description) && (
|
|
1683
1738
|
<div className="velu-hero">
|
|
1684
|
-
{frontmatter.title && <h1 id={pageId}>{frontmatter.title}</h1>}
|
|
1739
|
+
{frontmatter.title && <h1 id={site.theme === 'vepa' ? 'page-title' : pageId}>{frontmatter.title}</h1>}
|
|
1685
1740
|
{frontmatter.description && (
|
|
1686
1741
|
<p
|
|
1687
1742
|
style={{
|
|
@@ -1702,10 +1757,10 @@ function DocsPage() {
|
|
|
1702
1757
|
level. Falls back to a not-found / missing-file notice
|
|
1703
1758
|
when the route has no page (or its file is absent). */}
|
|
1704
1759
|
<MDXProvider components={defaultMdxComponents}>
|
|
1705
|
-
<mdx-content class="velu-prose">
|
|
1760
|
+
<mdx-content key={site.theme === 'vepa' ? pathname : undefined} id={site.theme === 'vepa' && !entry?.api ? 'content' : undefined} class="velu-prose">
|
|
1706
1761
|
{PageComponent ? (
|
|
1707
1762
|
<ErrorBoundary key={pathname} file={entry?.relPath}>
|
|
1708
|
-
<PageComponent />
|
|
1763
|
+
{entry?.mdxApi ? <ApiReferencePage {...entry.mdxApi} preset={site.theme}><PageComponent /></ApiReferencePage> : <PageComponent />}
|
|
1709
1764
|
</ErrorBoundary>
|
|
1710
1765
|
) : (
|
|
1711
1766
|
<p style={{ color: 'var(--muted-color)' }}>
|
|
@@ -1718,7 +1773,8 @@ function DocsPage() {
|
|
|
1718
1773
|
</MDXProvider>
|
|
1719
1774
|
{/* Page-foot feedback widget — ref'd so the sticky AskBar
|
|
1720
1775
|
above can hide as the user scrolls near it. */}
|
|
1721
|
-
|
|
1776
|
+
{chrome.articleChrome && (
|
|
1777
|
+
<div ref={setFeedbackEl} style={{ marginTop: 'var(--s3)' }}>
|
|
1722
1778
|
{/* key resets the widget's vote state on page change. "Yes"
|
|
1723
1779
|
submits immediately; "No" submits once its form is sent.
|
|
1724
1780
|
Handlers are no-ops in the dev preview (pageFeedback null). */}
|
|
@@ -1732,9 +1788,10 @@ function DocsPage() {
|
|
|
1732
1788
|
}
|
|
1733
1789
|
/>
|
|
1734
1790
|
</div>
|
|
1791
|
+
)}
|
|
1735
1792
|
{/* Previous / next page navigation — derived from the
|
|
1736
1793
|
sidebar reading order of the active section. */}
|
|
1737
|
-
{(nav?.prev || nav?.next) && (
|
|
1794
|
+
{chrome.articleChrome && !frontmatter.hidePagination && (nav?.prev || nav?.next) && (
|
|
1738
1795
|
<PageNav
|
|
1739
1796
|
id="pagination"
|
|
1740
1797
|
style={{ marginTop: 'var(--s2)' }}
|
|
@@ -1750,7 +1807,7 @@ function DocsPage() {
|
|
|
1750
1807
|
approaches (see IntersectionObserver above). */}
|
|
1751
1808
|
{/* Ask-a-question bar — opens the AI chatbot, which runs on the
|
|
1752
1809
|
deployed site. Hidden in the local dev preview. */}
|
|
1753
|
-
{!IS_DEV_PREVIEW && !chatOpen && !askAiHidden && (
|
|
1810
|
+
{!IS_DEV_PREVIEW && !chatOpen && !askAiHidden && chrome.articleChrome && (
|
|
1754
1811
|
<AskBar
|
|
1755
1812
|
onSubmit={askAI}
|
|
1756
1813
|
style={{
|
|
@@ -1776,14 +1833,14 @@ function DocsPage() {
|
|
|
1776
1833
|
configured social icons sit on the LEFT of this same row (no
|
|
1777
1834
|
full footer section); with link columns they live in the full
|
|
1778
1835
|
footer below instead. */}
|
|
1779
|
-
{footerHasLinks ? (
|
|
1780
|
-
<PoweredBy />
|
|
1836
|
+
{chrome.articleChrome && (footerHasLinks ? (
|
|
1837
|
+
site.theme === 'vepa' ? null : <PoweredBy />
|
|
1781
1838
|
) : (
|
|
1782
1839
|
<div className="velu-content-foot">
|
|
1783
1840
|
<SocialLinks socials={footerSocials} />
|
|
1784
1841
|
<PoweredBy />
|
|
1785
1842
|
</div>
|
|
1786
|
-
)}
|
|
1843
|
+
))}
|
|
1787
1844
|
</div>
|
|
1788
1845
|
</main>
|
|
1789
1846
|
</>
|
|
@@ -1794,15 +1851,16 @@ function DocsPage() {
|
|
|
1794
1851
|
full width below the article; its raised z-index eclipses the bottoms
|
|
1795
1852
|
of the fixed sidebar and TOC as the page scrolls into it, and the ref
|
|
1796
1853
|
is watched so the asides can pad their bottom by the overlap. */}
|
|
1797
|
-
{footerHasLinks && (
|
|
1854
|
+
{footerHasLinks && chrome.footer && (
|
|
1798
1855
|
<advanced-footer
|
|
1799
|
-
id=
|
|
1800
|
-
ref={
|
|
1856
|
+
id={site.theme === 'vepa' ? undefined : 'footer'}
|
|
1857
|
+
ref={setFooterEl}
|
|
1801
1858
|
data-velu-footer
|
|
1802
1859
|
style={{ position: 'relative', zIndex: 20 }}
|
|
1803
1860
|
>
|
|
1804
1861
|
<PageFooter
|
|
1805
|
-
|
|
1862
|
+
preset={site.theme}
|
|
1863
|
+
brand={{ label: site.name, logo: site.logo, href: site.logo?.href || '/' }}
|
|
1806
1864
|
columns={site.footer.links}
|
|
1807
1865
|
socials={footerSocials}
|
|
1808
1866
|
/>
|
|
@@ -1811,11 +1869,12 @@ function DocsPage() {
|
|
|
1811
1869
|
|
|
1812
1870
|
{/* Ask-AI chatbot — slides in from the inline-end edge. Hidden when the
|
|
1813
1871
|
docs owner's plan/credits disable Ask-AI. */}
|
|
1814
|
-
{!askAiHidden && (
|
|
1872
|
+
{!askAiHidden && (!chrome.assistant || !assistantUnavailable) && (
|
|
1815
1873
|
<Chatbot
|
|
1816
|
-
open={chatOpen}
|
|
1874
|
+
open={chrome.assistant || chatOpen}
|
|
1875
|
+
layout={chrome.assistant ? 'page' : 'panel'}
|
|
1817
1876
|
seedQuestion={chatQuestion}
|
|
1818
|
-
onClose={() => setChatOpen(false)}
|
|
1877
|
+
onClose={chrome.assistant ? undefined : () => setChatOpen(false)}
|
|
1819
1878
|
ask={assistant?.ask}
|
|
1820
1879
|
onFeedback={assistant?.sendFeedback}
|
|
1821
1880
|
listHistory={assistant?.listConversations}
|
|
@@ -1838,6 +1897,7 @@ function DocsPage() {
|
|
|
1838
1897
|
// behind the sheet. Desktop keeps the panel open so multi-cite hops
|
|
1839
1898
|
// stay convenient.
|
|
1840
1899
|
if (
|
|
1900
|
+
!chrome.assistant &&
|
|
1841
1901
|
typeof window !== 'undefined' &&
|
|
1842
1902
|
window.matchMedia('(max-width: 1024px)').matches
|
|
1843
1903
|
) {
|
|
@@ -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
|
+
|