@veluai/velu 0.2.35 → 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 +56 -0
- package/dist/cli.js +129 -54
- package/docs/mintlify-migration.md +159 -0
- package/docs/vepa.md +96 -0
- package/package.json +8 -2
- package/runtime/velu-ui/components/Accordion.jsx +3 -3
- package/runtime/velu-ui/components/ApiClient.jsx +4 -2
- package/runtime/velu-ui/components/ApiReferencePage.jsx +23 -15
- package/runtime/velu-ui/components/ApiSamples.jsx +9 -4
- package/runtime/velu-ui/components/AskBar.jsx +3 -2
- package/runtime/velu-ui/components/Callout.jsx +11 -26
- package/runtime/velu-ui/components/Card.jsx +16 -12
- package/runtime/velu-ui/components/ChangelogFilters.jsx +2 -1
- package/runtime/velu-ui/components/Chatbot.jsx +20 -9
- package/runtime/velu-ui/components/CodeBlock.jsx +7 -6
- package/runtime/velu-ui/components/Columns.jsx +2 -0
- package/runtime/velu-ui/components/ContextMenu.jsx +3 -2
- package/runtime/velu-ui/components/Field.jsx +2 -2
- package/runtime/velu-ui/components/Icon.jsx +1 -0
- package/runtime/velu-ui/components/Image.jsx +2 -2
- package/runtime/velu-ui/components/MethodBadge.jsx +2 -2
- package/runtime/velu-ui/components/NavSelect.jsx +22 -4
- package/runtime/velu-ui/components/NotFound.jsx +7 -7
- package/runtime/velu-ui/components/PageFeedback.jsx +13 -3
- package/runtime/velu-ui/components/PageFooter.jsx +3 -0
- package/runtime/velu-ui/components/PageHeader.jsx +37 -15
- package/runtime/velu-ui/components/PageNav.jsx +2 -1
- package/runtime/velu-ui/components/Prompt.jsx +2 -2
- package/runtime/velu-ui/components/Search.jsx +1 -0
- package/runtime/velu-ui/components/Sidebar.jsx +18 -6
- package/runtime/velu-ui/components/Steps.jsx +4 -4
- package/runtime/velu-ui/components/ThemePreferenceMenu.jsx +57 -0
- package/runtime/velu-ui/components/ThemeToggle.jsx +4 -1
- package/runtime/velu-ui/components/Toc.jsx +6 -4
- package/runtime/velu-ui/components/Tree.jsx +6 -5
- package/runtime/velu-ui/components/TryItBar.jsx +1 -0
- package/runtime/velu-ui/components/Update.jsx +2 -2
- package/runtime/velu-ui/components/VepaFooter.jsx +40 -0
- package/runtime/velu-ui/components/callout.css +28 -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 +80 -0
- package/runtime/velu-ui/components/page-header.css +6 -0
- package/runtime/velu-ui/components/sidebar.css +5 -5
- package/runtime/velu-ui/element-selectors.css +66 -0
- package/runtime/velu-ui/primitives/Cluster.jsx +12 -0
- package/runtime/velu-ui/primitives/Stack.jsx +12 -0
- package/runtime/velu-ui/primitives/Switcher.jsx +11 -1
- package/runtime/velu-ui/styles.css +52 -35
- 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 +144 -52
- package/src/runtime/VepaToc.jsx +12 -0
- package/src/runtime/client-entry.jsx +23 -0
- package/src/runtime/dev-warnings.js +16 -0
- package/src/runtime/page-mode.js +54 -0
- package/src/runtime/server-entry.jsx +1 -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';
|
|
@@ -683,6 +686,19 @@ function ImageDemo() {
|
|
|
683
686
|
|
|
684
687
|
const RouterLink = ({ href, ...rest }) => <Link to={href} {...rest} />;
|
|
685
688
|
|
|
689
|
+
// Look up a heading by its slug id. Prefers a match INSIDE the article: the
|
|
690
|
+
// layout chrome carries stable, Mintlify-compatible ids for custom CSS
|
|
691
|
+
// (#sidebar, #content, #footer, …), and a heading titled "Sidebar" or "Footer"
|
|
692
|
+
// would slug to the same id — document.getElementById would then return the
|
|
693
|
+
// chrome element (earlier in DOM order) and the TOC would scroll to the wrong
|
|
694
|
+
// place.
|
|
695
|
+
function sectionById(id) {
|
|
696
|
+
if (typeof document === 'undefined' || !id) return null;
|
|
697
|
+
const article = document.querySelector('.velu-docs-layout__article');
|
|
698
|
+
const esc = id.replace(/["\\]/g, '\\$&');
|
|
699
|
+
return article?.querySelector(`[id="${esc}"]`) ?? document.getElementById(id);
|
|
700
|
+
}
|
|
701
|
+
|
|
686
702
|
// Flatten a nested TOC tree to {id, label, depth} for scroll-spy.
|
|
687
703
|
function flat(nodes, depth = 0, out = []) {
|
|
688
704
|
for (const n of nodes) {
|
|
@@ -730,7 +746,7 @@ function useScrollSpy(ids) {
|
|
|
730
746
|
let best = ids[0];
|
|
731
747
|
let bestDist = Infinity;
|
|
732
748
|
for (const id of ids) {
|
|
733
|
-
const node =
|
|
749
|
+
const node = sectionById(id);
|
|
734
750
|
if (!node) continue;
|
|
735
751
|
const top = node.getBoundingClientRect().top;
|
|
736
752
|
if (top >= vh) continue;
|
|
@@ -842,6 +858,10 @@ function DocsPage() {
|
|
|
842
858
|
// No renderable page for this route → show the 404 page (a clean centered
|
|
843
859
|
// takeover: header + footer stay, the docs sidebar/TOC are hidden).
|
|
844
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);
|
|
845
865
|
|
|
846
866
|
// Switcher option sets (only render a switcher when an axis has >1
|
|
847
867
|
// option). Anchors are pinned sidebar links shown in the context zone.
|
|
@@ -867,6 +887,7 @@ function DocsPage() {
|
|
|
867
887
|
options={languageOptions}
|
|
868
888
|
linkComponent={RouterLink}
|
|
869
889
|
ariaLabel="Language"
|
|
890
|
+
idPrefix="localization-select"
|
|
870
891
|
/>
|
|
871
892
|
);
|
|
872
893
|
const productSwitcher = productOptions.length > 1 && (
|
|
@@ -875,6 +896,7 @@ function DocsPage() {
|
|
|
875
896
|
options={productOptions}
|
|
876
897
|
linkComponent={RouterLink}
|
|
877
898
|
ariaLabel="Product"
|
|
899
|
+
component="nav-dropdown-products-selector"
|
|
878
900
|
/>
|
|
879
901
|
);
|
|
880
902
|
|
|
@@ -897,14 +919,18 @@ function DocsPage() {
|
|
|
897
919
|
}
|
|
898
920
|
descTag.setAttribute('content', frontmatter.description || '');
|
|
899
921
|
document.documentElement.lang = nav?.activeLanguageCode || 'en';
|
|
922
|
+
// Mirrors the SSR'd attribute so custom CSS scoped with
|
|
923
|
+
// html[data-current-path="/x"] follows client-side navigation too.
|
|
924
|
+
document.documentElement.dataset.currentPath = pathname;
|
|
900
925
|
}, [pathname, isNotFound, frontmatter.title, frontmatter.description, nav?.activeLanguageCode]);
|
|
901
926
|
|
|
902
927
|
// Frontmatter title needs an id so scroll-spy + click-to-scroll work
|
|
903
928
|
// against it like any other heading.
|
|
904
929
|
const pageId = React.useMemo(() => {
|
|
905
930
|
if (!frontmatter.title) return null;
|
|
931
|
+
if (site.theme === 'vepa') return 'page-title';
|
|
906
932
|
return new GithubSlugger().slug(frontmatter.title);
|
|
907
|
-
}, [frontmatter.title]);
|
|
933
|
+
}, [frontmatter.title, site.theme]);
|
|
908
934
|
|
|
909
935
|
// Combined TOC: the frontmatter title is the page's top-level entry,
|
|
910
936
|
// and the MDX-derived headings (h2s) become its children. Without this,
|
|
@@ -1040,7 +1066,11 @@ function DocsPage() {
|
|
|
1040
1066
|
const tocBarToggle = document.querySelector('.velu-toc-bar__toggle');
|
|
1041
1067
|
if (tocBarToggle) ro.observe(tocBarToggle);
|
|
1042
1068
|
return () => ro.disconnect();
|
|
1043
|
-
|
|
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]);
|
|
1044
1074
|
|
|
1045
1075
|
// The asides (left sidebar + right TOC) scroll independently of the
|
|
1046
1076
|
// page. On scroll/resize we set data-fade-top/-bottom on each scroll
|
|
@@ -1161,17 +1191,26 @@ function DocsPage() {
|
|
|
1161
1191
|
label: 'Log out',
|
|
1162
1192
|
onClick: logoutPrivateAccess,
|
|
1163
1193
|
kind: 'outlined',
|
|
1194
|
+
component: 'logout-link',
|
|
1164
1195
|
});
|
|
1165
1196
|
}
|
|
1166
1197
|
|
|
1167
|
-
|
|
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);
|
|
1168
1204
|
const [footerOverlap, setFooterOverlap] = React.useState(0);
|
|
1169
1205
|
React.useEffect(() => {
|
|
1170
|
-
|
|
1171
|
-
|
|
1206
|
+
if (!footerEl) {
|
|
1207
|
+
setFooterOverlap(0);
|
|
1208
|
+
return undefined;
|
|
1209
|
+
}
|
|
1172
1210
|
const update = () => {
|
|
1173
|
-
const rect =
|
|
1174
|
-
|
|
1211
|
+
const rect = footerEl.getBoundingClientRect();
|
|
1212
|
+
const visible = Math.max(0, window.innerHeight - rect.top);
|
|
1213
|
+
setFooterOverlap(Math.min(visible, footerEl.offsetHeight));
|
|
1175
1214
|
};
|
|
1176
1215
|
update();
|
|
1177
1216
|
window.addEventListener('scroll', update, { passive: true });
|
|
@@ -1180,15 +1219,17 @@ function DocsPage() {
|
|
|
1180
1219
|
window.removeEventListener('scroll', update);
|
|
1181
1220
|
window.removeEventListener('resize', update);
|
|
1182
1221
|
};
|
|
1183
|
-
}, []);
|
|
1222
|
+
}, [footerEl]);
|
|
1184
1223
|
|
|
1185
1224
|
// Sticky AskBar fades out once the user scrolls near the PageFeedback
|
|
1186
1225
|
// widget — so it doesn't sit on top of the page-foot widgets.
|
|
1187
|
-
const
|
|
1226
|
+
const [feedbackEl, setFeedbackEl] = React.useState(null);
|
|
1188
1227
|
const [askBarHidden, setAskBarHidden] = React.useState(false);
|
|
1189
1228
|
React.useEffect(() => {
|
|
1190
|
-
|
|
1191
|
-
|
|
1229
|
+
if (!feedbackEl) {
|
|
1230
|
+
setAskBarHidden(false);
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1192
1233
|
const FADE_BUFFER = 64; // matches the rootMargin below
|
|
1193
1234
|
const apply = (rect) => {
|
|
1194
1235
|
// Hide whenever the feedback's top has reached the (effective)
|
|
@@ -1198,20 +1239,20 @@ function DocsPage() {
|
|
|
1198
1239
|
// exactly at the same line in both scroll directions.
|
|
1199
1240
|
setAskBarHidden(rect.top < window.innerHeight - FADE_BUFFER);
|
|
1200
1241
|
};
|
|
1201
|
-
apply(
|
|
1242
|
+
apply(feedbackEl.getBoundingClientRect());
|
|
1202
1243
|
const io = new IntersectionObserver(
|
|
1203
1244
|
([entry]) => apply(entry.boundingClientRect),
|
|
1204
1245
|
// Trigger ~64px before the feedback enters the viewport, so the
|
|
1205
1246
|
// AskBar fades just as the widget starts to peek up from below.
|
|
1206
1247
|
{ rootMargin: '0px 0px -64px 0px' },
|
|
1207
1248
|
);
|
|
1208
|
-
io.observe(
|
|
1249
|
+
io.observe(feedbackEl);
|
|
1209
1250
|
return () => io.disconnect();
|
|
1210
|
-
}, []);
|
|
1251
|
+
}, [feedbackEl]);
|
|
1211
1252
|
|
|
1212
1253
|
const scrollTo = React.useCallback(
|
|
1213
1254
|
(id) => {
|
|
1214
|
-
const node =
|
|
1255
|
+
const node = sectionById(id);
|
|
1215
1256
|
if (!node) return;
|
|
1216
1257
|
// 1) Set + lock the active id IMMEDIATELY so the spy doesn't
|
|
1217
1258
|
// overwrite it during the smooth-scroll animation.
|
|
@@ -1242,12 +1283,15 @@ function DocsPage() {
|
|
|
1242
1283
|
|
|
1243
1284
|
return (
|
|
1244
1285
|
<div
|
|
1286
|
+
id="body-content"
|
|
1245
1287
|
className="velu-docs-layout"
|
|
1246
|
-
data-
|
|
1288
|
+
data-site-theme={site.theme ?? 'velu'}
|
|
1289
|
+
data-chat-open={chatOpen && !chrome.assistant ? 'true' : 'false'}
|
|
1247
1290
|
data-sidebar-open={sidebarOpen ? 'true' : 'false'}
|
|
1248
1291
|
data-drawer-open={drawerOpen ? 'true' : 'false'}
|
|
1249
1292
|
data-api={entry?.api ? 'true' : 'false'}
|
|
1250
1293
|
data-not-found={isNotFound ? 'true' : undefined}
|
|
1294
|
+
data-page-mode={isNotFound ? undefined : chrome.mode}
|
|
1251
1295
|
>
|
|
1252
1296
|
{/* Scrim — visible at mobile while the drawer OR the chatbot
|
|
1253
1297
|
sheet is open. Sits between the article (z-0) and the
|
|
@@ -1264,6 +1308,8 @@ function DocsPage() {
|
|
|
1264
1308
|
{/* Site header — brand, centered search, right-side actions,
|
|
1265
1309
|
tabs row. Configurable: pass any number of actions / tabs. */}
|
|
1266
1310
|
<PageHeader
|
|
1311
|
+
preset={site.theme}
|
|
1312
|
+
id="navbar"
|
|
1267
1313
|
linkComponent={RouterLink}
|
|
1268
1314
|
brand={{
|
|
1269
1315
|
label: site.name,
|
|
@@ -1275,10 +1321,10 @@ function DocsPage() {
|
|
|
1275
1321
|
<span className="velu-hide-on-mobile">{versionSwitcher}</span>
|
|
1276
1322
|
)
|
|
1277
1323
|
}
|
|
1278
|
-
tabsTrailing={languageSwitcher || undefined}
|
|
1279
1324
|
center={
|
|
1280
1325
|
<Cluster space="var(--s-6)" align="center">
|
|
1281
1326
|
<Search
|
|
1327
|
+
id="search-bar-entry"
|
|
1282
1328
|
style={{ inlineSize: '30ch' }}
|
|
1283
1329
|
unavailable={IS_DEV_PREVIEW}
|
|
1284
1330
|
search={IS_DEV_PREVIEW ? undefined : searchDocs}
|
|
@@ -1287,9 +1333,10 @@ function DocsPage() {
|
|
|
1287
1333
|
{/* Ask AI talks to the deployed site's AI backend — hidden in
|
|
1288
1334
|
the local dev preview where there's nothing to talk to, and
|
|
1289
1335
|
when the owner's plan/credits disable the assistant. */}
|
|
1290
|
-
{!IS_DEV_PREVIEW && !askAiHidden && (
|
|
1336
|
+
{!IS_DEV_PREVIEW && !askAiHidden && !chrome.assistant && (
|
|
1291
1337
|
<button
|
|
1292
1338
|
type="button"
|
|
1339
|
+
id="assistant-entry"
|
|
1293
1340
|
className="velu-header__action velu-header__action--outlined"
|
|
1294
1341
|
onClick={() => askAI('')}
|
|
1295
1342
|
>
|
|
@@ -1302,11 +1349,24 @@ function DocsPage() {
|
|
|
1302
1349
|
</Cluster>
|
|
1303
1350
|
}
|
|
1304
1351
|
actions={navActions}
|
|
1305
|
-
trailing={<ThemeToggle />}
|
|
1306
|
-
|
|
1307
|
-
|
|
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
|
+
}
|
|
1308
1364
|
activeTab={nav?.activeTab}
|
|
1309
|
-
tabs={
|
|
1365
|
+
tabs={
|
|
1366
|
+
chrome.mode === 'custom' || chrome.mode === 'assistant'
|
|
1367
|
+
? []
|
|
1368
|
+
: (nav?.tabs ?? [])
|
|
1369
|
+
}
|
|
1310
1370
|
/>
|
|
1311
1371
|
|
|
1312
1372
|
{/* Fixed left sidebar — pinned to viewport-left below the header.
|
|
@@ -1316,6 +1376,7 @@ function DocsPage() {
|
|
|
1316
1376
|
honour `inset-block-start` for fixed positioning the same way
|
|
1317
1377
|
as plain `top`). */}
|
|
1318
1378
|
<aside
|
|
1379
|
+
id={site.theme === 'vepa' ? 'sidebar-content' : 'sidebar'}
|
|
1319
1380
|
className="velu-docs-layout__aside velu-docs-layout__aside--left"
|
|
1320
1381
|
style={{
|
|
1321
1382
|
/* Bottom edge stays a fixed gap above the viewport bottom,
|
|
@@ -1400,6 +1461,8 @@ function DocsPage() {
|
|
|
1400
1461
|
<li key={it.href} role="none">
|
|
1401
1462
|
<a
|
|
1402
1463
|
role="menuitem"
|
|
1464
|
+
data-component="mobile-nav-tabs-item"
|
|
1465
|
+
data-active={it.href === nav?.activeTab ? 'true' : undefined}
|
|
1403
1466
|
className="velu-docs-layout__drawer-docselect-item"
|
|
1404
1467
|
href={it.href}
|
|
1405
1468
|
tabIndex={navOpen ? 0 : -1}
|
|
@@ -1429,10 +1492,11 @@ function DocsPage() {
|
|
|
1429
1492
|
<span className="velu-show-on-mobile">{languageSwitcher}</span>
|
|
1430
1493
|
)}
|
|
1431
1494
|
{anchors.length > 0 && (
|
|
1432
|
-
<ul className="velu-docs-anchors">
|
|
1495
|
+
<ul className="velu-docs-anchors" data-component="nav-anchors">
|
|
1433
1496
|
{anchors.map((a, i) => (
|
|
1434
1497
|
<li key={i}>
|
|
1435
1498
|
<a
|
|
1499
|
+
data-component="nav-anchor"
|
|
1436
1500
|
className="velu-docs-anchors__link"
|
|
1437
1501
|
href={a.href}
|
|
1438
1502
|
target="_blank"
|
|
@@ -1457,7 +1521,7 @@ function DocsPage() {
|
|
|
1457
1521
|
{/* Hairline separating the top anchor links from the nav sections
|
|
1458
1522
|
(faithful to the sidebar design's anchor↔sidebar divider). */}
|
|
1459
1523
|
{anchors.length > 0 && (
|
|
1460
|
-
<
|
|
1524
|
+
<sidebar-nav-group-divider class="velu-docs-context-divider" aria-hidden="true" />
|
|
1461
1525
|
)}
|
|
1462
1526
|
{/* Only this region scrolls — the context zone above stays
|
|
1463
1527
|
pinned. The up/down arrows overlay its top/bottom edges and
|
|
@@ -1466,6 +1530,7 @@ function DocsPage() {
|
|
|
1466
1530
|
nav fully to that end. */}
|
|
1467
1531
|
<div className="velu-docs-nav-region">
|
|
1468
1532
|
<div
|
|
1533
|
+
id={site.theme === 'vepa' ? 'navigation-items' : 'sidebar-content'}
|
|
1469
1534
|
ref={leftAsideRef}
|
|
1470
1535
|
className="velu-docs-nav-scroll velu-hide-scrollbar"
|
|
1471
1536
|
style={{
|
|
@@ -1533,9 +1598,11 @@ function DocsPage() {
|
|
|
1533
1598
|
display:none snap. See docs-layout.css. */}
|
|
1534
1599
|
|
|
1535
1600
|
{/* Fixed right TOC — pinned to viewport-right. Hidden while the
|
|
1536
|
-
Ask-AI chatbot is open (the panel takes that edge).
|
|
1537
|
-
|
|
1601
|
+
Ask-AI chatbot is open (the panel takes that edge). Wide /
|
|
1602
|
+
custom / frame / center / assistant hide this rail. */}
|
|
1603
|
+
{!chatOpen && chrome.toc && (
|
|
1538
1604
|
<aside
|
|
1605
|
+
id="table-of-contents"
|
|
1539
1606
|
ref={rightAsideRef}
|
|
1540
1607
|
className="velu-docs-layout__aside velu-docs-layout__aside--right velu-hide-scrollbar"
|
|
1541
1608
|
style={{
|
|
@@ -1549,13 +1616,15 @@ function DocsPage() {
|
|
|
1549
1616
|
(where the TOC sits for normal pages). */}
|
|
1550
1617
|
{entry?.api && entry.operation ? (
|
|
1551
1618
|
<ApiSamples
|
|
1619
|
+
preset={site.theme}
|
|
1620
|
+
title={frontmatter.title}
|
|
1552
1621
|
samples={entry.samples}
|
|
1553
1622
|
responses={entry.operation.responses}
|
|
1554
1623
|
/>
|
|
1555
1624
|
) : isChangelog && changelogTags.length ? (
|
|
1556
1625
|
<ChangelogFilters tags={changelogTags} />
|
|
1557
1626
|
) : (
|
|
1558
|
-
<Toc 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} />
|
|
1559
1628
|
)}
|
|
1560
1629
|
</aside>
|
|
1561
1630
|
)}
|
|
@@ -1578,7 +1647,14 @@ function DocsPage() {
|
|
|
1578
1647
|
onAskAI={IS_DEV_PREVIEW || askAiHidden ? undefined : () => askAI('')}
|
|
1579
1648
|
/>
|
|
1580
1649
|
</main>
|
|
1581
|
-
) : (
|
|
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 : (
|
|
1582
1658
|
<>
|
|
1583
1659
|
{/* Narrow-layout TOC bar — always in DOM, only visible at
|
|
1584
1660
|
< 1024px (toggled by @container in toc-bar.css). Shares
|
|
@@ -1587,9 +1663,13 @@ function DocsPage() {
|
|
|
1587
1663
|
data; whichever one is visible at a given width responds
|
|
1588
1664
|
to the same scroll-spy state. */}
|
|
1589
1665
|
{/* Changelog tag filters are a wide-layout (right-rail) affordance only;
|
|
1590
|
-
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 && (
|
|
1591
1669
|
<TocBar items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1670
|
+
)}
|
|
1592
1671
|
<main
|
|
1672
|
+
id="content-area"
|
|
1593
1673
|
className="velu-docs-layout__main"
|
|
1594
1674
|
style={{
|
|
1595
1675
|
/* No padding-block-end — the article's last child (PoweredBy)
|
|
@@ -1611,6 +1691,7 @@ function DocsPage() {
|
|
|
1611
1691
|
column, and therefore sits BELOW the TocBar rather than
|
|
1612
1692
|
overlapping it. Chevron flips direction with the
|
|
1613
1693
|
`data-sidebar-open` data attribute on the layout root. */}
|
|
1694
|
+
{chrome.sidebar && (
|
|
1614
1695
|
<button
|
|
1615
1696
|
type="button"
|
|
1616
1697
|
className="velu-docs-layout__sidebar-toggle"
|
|
@@ -1624,11 +1705,13 @@ function DocsPage() {
|
|
|
1624
1705
|
})}
|
|
1625
1706
|
</span>
|
|
1626
1707
|
</button>
|
|
1627
|
-
|
|
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 : ""}>
|
|
1628
1710
|
{/* Per-page agent/IDE action bar: the section eyebrow + a
|
|
1629
1711
|
"Copy Page" split-button whose dropdown is driven by the
|
|
1630
1712
|
Mintlify-compatible `contextual` config. Renders nothing
|
|
1631
1713
|
when there's neither an eyebrow nor any enabled options. */}
|
|
1714
|
+
{chrome.articleChrome && (
|
|
1632
1715
|
<ContextMenu
|
|
1633
1716
|
eyebrow={
|
|
1634
1717
|
nav?.breadcrumb && nav.breadcrumb.length > 1
|
|
@@ -1645,13 +1728,15 @@ function DocsPage() {
|
|
|
1645
1728
|
getMarkdown={entry?.getSource}
|
|
1646
1729
|
rssHref={isChangelog ? `${BASE_PATH}/rss.xml` : undefined}
|
|
1647
1730
|
/>
|
|
1731
|
+
)}
|
|
1648
1732
|
|
|
1649
1733
|
{/* Page hero from MDX frontmatter. `.velu-hero` rules
|
|
1650
1734
|
(in base.css) keep the title and description tightly
|
|
1651
|
-
grouped and create a clear break before the prose body.
|
|
1652
|
-
|
|
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) && (
|
|
1653
1738
|
<div className="velu-hero">
|
|
1654
|
-
{frontmatter.title && <h1 id={pageId}>{frontmatter.title}</h1>}
|
|
1739
|
+
{frontmatter.title && <h1 id={site.theme === 'vepa' ? 'page-title' : pageId}>{frontmatter.title}</h1>}
|
|
1655
1740
|
{frontmatter.description && (
|
|
1656
1741
|
<p
|
|
1657
1742
|
style={{
|
|
@@ -1672,10 +1757,10 @@ function DocsPage() {
|
|
|
1672
1757
|
level. Falls back to a not-found / missing-file notice
|
|
1673
1758
|
when the route has no page (or its file is absent). */}
|
|
1674
1759
|
<MDXProvider components={defaultMdxComponents}>
|
|
1675
|
-
<
|
|
1760
|
+
<mdx-content key={site.theme === 'vepa' ? pathname : undefined} id={site.theme === 'vepa' && !entry?.api ? 'content' : undefined} class="velu-prose">
|
|
1676
1761
|
{PageComponent ? (
|
|
1677
1762
|
<ErrorBoundary key={pathname} file={entry?.relPath}>
|
|
1678
|
-
<PageComponent />
|
|
1763
|
+
{entry?.mdxApi ? <ApiReferencePage {...entry.mdxApi} preset={site.theme}><PageComponent /></ApiReferencePage> : <PageComponent />}
|
|
1679
1764
|
</ErrorBoundary>
|
|
1680
1765
|
) : (
|
|
1681
1766
|
<p style={{ color: 'var(--muted-color)' }}>
|
|
@@ -1684,11 +1769,12 @@ function DocsPage() {
|
|
|
1684
1769
|
: 'Page not found.'}
|
|
1685
1770
|
</p>
|
|
1686
1771
|
)}
|
|
1687
|
-
</
|
|
1772
|
+
</mdx-content>
|
|
1688
1773
|
</MDXProvider>
|
|
1689
1774
|
{/* Page-foot feedback widget — ref'd so the sticky AskBar
|
|
1690
1775
|
above can hide as the user scrolls near it. */}
|
|
1691
|
-
|
|
1776
|
+
{chrome.articleChrome && (
|
|
1777
|
+
<div ref={setFeedbackEl} style={{ marginTop: 'var(--s3)' }}>
|
|
1692
1778
|
{/* key resets the widget's vote state on page change. "Yes"
|
|
1693
1779
|
submits immediately; "No" submits once its form is sent.
|
|
1694
1780
|
Handlers are no-ops in the dev preview (pageFeedback null). */}
|
|
@@ -1702,10 +1788,12 @@ function DocsPage() {
|
|
|
1702
1788
|
}
|
|
1703
1789
|
/>
|
|
1704
1790
|
</div>
|
|
1791
|
+
)}
|
|
1705
1792
|
{/* Previous / next page navigation — derived from the
|
|
1706
1793
|
sidebar reading order of the active section. */}
|
|
1707
|
-
{(nav?.prev || nav?.next) && (
|
|
1794
|
+
{chrome.articleChrome && !frontmatter.hidePagination && (nav?.prev || nav?.next) && (
|
|
1708
1795
|
<PageNav
|
|
1796
|
+
id="pagination"
|
|
1709
1797
|
style={{ marginTop: 'var(--s2)' }}
|
|
1710
1798
|
prev={nav?.prev}
|
|
1711
1799
|
next={nav?.next}
|
|
@@ -1719,7 +1807,7 @@ function DocsPage() {
|
|
|
1719
1807
|
approaches (see IntersectionObserver above). */}
|
|
1720
1808
|
{/* Ask-a-question bar — opens the AI chatbot, which runs on the
|
|
1721
1809
|
deployed site. Hidden in the local dev preview. */}
|
|
1722
|
-
{!IS_DEV_PREVIEW && !chatOpen && !askAiHidden && (
|
|
1810
|
+
{!IS_DEV_PREVIEW && !chatOpen && !askAiHidden && chrome.articleChrome && (
|
|
1723
1811
|
<AskBar
|
|
1724
1812
|
onSubmit={askAI}
|
|
1725
1813
|
style={{
|
|
@@ -1745,14 +1833,14 @@ function DocsPage() {
|
|
|
1745
1833
|
configured social icons sit on the LEFT of this same row (no
|
|
1746
1834
|
full footer section); with link columns they live in the full
|
|
1747
1835
|
footer below instead. */}
|
|
1748
|
-
{footerHasLinks ? (
|
|
1749
|
-
<PoweredBy />
|
|
1836
|
+
{chrome.articleChrome && (footerHasLinks ? (
|
|
1837
|
+
site.theme === 'vepa' ? null : <PoweredBy />
|
|
1750
1838
|
) : (
|
|
1751
1839
|
<div className="velu-content-foot">
|
|
1752
1840
|
<SocialLinks socials={footerSocials} />
|
|
1753
1841
|
<PoweredBy />
|
|
1754
1842
|
</div>
|
|
1755
|
-
)}
|
|
1843
|
+
))}
|
|
1756
1844
|
</div>
|
|
1757
1845
|
</main>
|
|
1758
1846
|
</>
|
|
@@ -1763,27 +1851,30 @@ function DocsPage() {
|
|
|
1763
1851
|
full width below the article; its raised z-index eclipses the bottoms
|
|
1764
1852
|
of the fixed sidebar and TOC as the page scrolls into it, and the ref
|
|
1765
1853
|
is watched so the asides can pad their bottom by the overlap. */}
|
|
1766
|
-
{footerHasLinks && (
|
|
1767
|
-
<
|
|
1768
|
-
|
|
1854
|
+
{footerHasLinks && chrome.footer && (
|
|
1855
|
+
<advanced-footer
|
|
1856
|
+
id={site.theme === 'vepa' ? undefined : 'footer'}
|
|
1857
|
+
ref={setFooterEl}
|
|
1769
1858
|
data-velu-footer
|
|
1770
1859
|
style={{ position: 'relative', zIndex: 20 }}
|
|
1771
1860
|
>
|
|
1772
1861
|
<PageFooter
|
|
1773
|
-
|
|
1862
|
+
preset={site.theme}
|
|
1863
|
+
brand={{ label: site.name, logo: site.logo, href: site.logo?.href || '/' }}
|
|
1774
1864
|
columns={site.footer.links}
|
|
1775
1865
|
socials={footerSocials}
|
|
1776
1866
|
/>
|
|
1777
|
-
</
|
|
1867
|
+
</advanced-footer>
|
|
1778
1868
|
)}
|
|
1779
1869
|
|
|
1780
1870
|
{/* Ask-AI chatbot — slides in from the inline-end edge. Hidden when the
|
|
1781
1871
|
docs owner's plan/credits disable Ask-AI. */}
|
|
1782
|
-
{!askAiHidden && (
|
|
1872
|
+
{!askAiHidden && (!chrome.assistant || !assistantUnavailable) && (
|
|
1783
1873
|
<Chatbot
|
|
1784
|
-
open={chatOpen}
|
|
1874
|
+
open={chrome.assistant || chatOpen}
|
|
1875
|
+
layout={chrome.assistant ? 'page' : 'panel'}
|
|
1785
1876
|
seedQuestion={chatQuestion}
|
|
1786
|
-
onClose={() => setChatOpen(false)}
|
|
1877
|
+
onClose={chrome.assistant ? undefined : () => setChatOpen(false)}
|
|
1787
1878
|
ask={assistant?.ask}
|
|
1788
1879
|
onFeedback={assistant?.sendFeedback}
|
|
1789
1880
|
listHistory={assistant?.listConversations}
|
|
@@ -1806,6 +1897,7 @@ function DocsPage() {
|
|
|
1806
1897
|
// behind the sheet. Desktop keeps the panel open so multi-cite hops
|
|
1807
1898
|
// stay convenient.
|
|
1808
1899
|
if (
|
|
1900
|
+
!chrome.assistant &&
|
|
1809
1901
|
typeof window !== 'undefined' &&
|
|
1810
1902
|
window.matchMedia('(max-width: 1024px)').matches
|
|
1811
1903
|
) {
|
|
@@ -1817,7 +1909,7 @@ function DocsPage() {
|
|
|
1817
1909
|
let tries = 0;
|
|
1818
1910
|
const go = () => {
|
|
1819
1911
|
if (hash) {
|
|
1820
|
-
if (
|
|
1912
|
+
if (sectionById(hash)) { scrollTo(hash); return; }
|
|
1821
1913
|
if (tries++ < 10) { setTimeout(go, 60); return; }
|
|
1822
1914
|
}
|
|
1823
1915
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import './dev-warnings.js';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import { hydrateRoot } from 'react-dom/client';
|
|
3
4
|
import { BrowserRouter } from 'react-router-dom';
|
|
4
5
|
import 'velu-ui/styles.css';
|
|
6
|
+
import { site } from 'virtual:velu-site';
|
|
5
7
|
import App from './App.jsx';
|
|
6
8
|
|
|
7
9
|
// Subpath hosting: Vite sets BASE_URL from the build's `base` (e.g. "/docs/").
|
|
@@ -9,10 +11,31 @@ import App from './App.jsx';
|
|
|
9
11
|
// case ("/") becomes undefined so the router behaves as if mounted at origin.
|
|
10
12
|
const basename = (import.meta.env.BASE_URL || '/').replace(/\/$/, '') || undefined;
|
|
11
13
|
|
|
14
|
+
// Custom JavaScript (Mintlify-compatible): every `.js` file in the project is
|
|
15
|
+
// run on every page. Loaded from an effect so it runs AFTER hydration commits —
|
|
16
|
+
// the page is interactive and a script that mutates the DOM can't cause a
|
|
17
|
+
// hydration mismatch. Classic (non-module) scripts, appended in the discovered
|
|
18
|
+
// order; `async = false` keeps that order for dynamically inserted scripts.
|
|
19
|
+
// Runs once per full page load, not per client-side navigation — same as a
|
|
20
|
+
// <script> tag in the HTML would.
|
|
21
|
+
function CustomScripts() {
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
for (const url of site.customScripts ?? []) {
|
|
24
|
+
const el = document.createElement('script');
|
|
25
|
+
el.src = (basename || '') + url;
|
|
26
|
+
el.async = false;
|
|
27
|
+
el.dataset.veluCustom = '';
|
|
28
|
+
document.body.appendChild(el);
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
hydrateRoot(
|
|
13
35
|
document.getElementById('root'),
|
|
14
36
|
<BrowserRouter basename={basename}>
|
|
15
37
|
<App />
|
|
38
|
+
<CustomScripts />
|
|
16
39
|
</BrowserRouter>
|
|
17
40
|
);
|
|
18
41
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// velu-ui renders component roots as Mintlify-compatible custom tags
|
|
2
|
+
// (`<card>`, `<callout>`, `<code-block>`, …) so project CSS can use element
|
|
3
|
+
// selectors. Browsers handle unknown elements fine (HTMLUnknownElement), and
|
|
4
|
+
// React renders them fine too, but React's DEVELOPMENT build logs
|
|
5
|
+
// "Warning: The tag <card> is unrecognized in this browser. …"
|
|
6
|
+
// once per tag name for the ones without a hyphen. Production builds never
|
|
7
|
+
// emit it. This drops exactly that message in dev (server + browser) so
|
|
8
|
+
// `velu dev` output stays readable; every other console.error passes through.
|
|
9
|
+
if (import.meta.env.DEV && typeof console !== 'undefined') {
|
|
10
|
+
const original = console.error;
|
|
11
|
+
console.error = function veluFilteredError(...args) {
|
|
12
|
+
const first = args[0];
|
|
13
|
+
if (typeof first === 'string' && first.includes('is unrecognized in this browser')) return;
|
|
14
|
+
original.apply(this, args);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -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
|
+
}
|