@veluai/velu 0.2.37 → 0.2.39
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/cli.js +41 -41
- package/docs/aalam.md +44 -0
- package/docs/mintlify-migration.md +8 -3
- package/docs/thulir.md +23 -0
- package/docs/vepa.md +2 -1
- package/package.json +4 -2
- package/runtime/velu-ui/base.css +9 -0
- package/runtime/velu-ui/components/ApiReferencePage.jsx +4 -3
- package/runtime/velu-ui/components/ApiSamples.jsx +9 -2
- package/runtime/velu-ui/components/Callout.jsx +4 -1
- package/runtime/velu-ui/components/PageFooter.jsx +3 -1
- package/runtime/velu-ui/components/PageHeader.jsx +7 -1
- package/runtime/velu-ui/components/PageNav.jsx +98 -22
- package/runtime/velu-ui/components/Sidebar.jsx +21 -9
- package/runtime/velu-ui/components/ThemeToggle.jsx +1 -0
- package/runtime/velu-ui/components/TocBar.jsx +21 -2
- package/runtime/velu-ui/components/Update.jsx +22 -10
- package/runtime/velu-ui/components/VepaFooter.jsx +19 -17
- package/runtime/velu-ui/components/api-page.css +6 -3
- package/runtime/velu-ui/components/chatbot.css +5 -4
- package/runtime/velu-ui/components/docs-layout.css +99 -114
- package/runtime/velu-ui/components/page-footer.css +2 -3
- package/runtime/velu-ui/components/page-header.css +6 -0
- package/runtime/velu-ui/components/page-nav.css +192 -0
- package/runtime/velu-ui/components/sidebar.css +5 -13
- package/runtime/velu-ui/primitives/switcher.css +4 -0
- package/runtime/velu-ui/styles.css +2 -0
- package/runtime/velu-ui/themes/aalam.css +266 -0
- package/runtime/velu-ui/themes/thulir.css +428 -0
- package/runtime/velu-ui/themes/vepa.css +142 -10
- package/schema/velu.schema.json +23 -3
- package/src/navigation.js +22 -3
- package/src/runtime/App.jsx +182 -107
- package/templates/starter/ai-tools/claude-code.mdx +1 -0
- package/templates/starter/ai-tools/cursor.mdx +1 -0
- package/templates/starter/api-reference/introduction.mdx +1 -0
- package/templates/starter/development.mdx +1 -0
- package/templates/starter/essentials/code.mdx +1 -0
- package/templates/starter/essentials/images.mdx +1 -0
- package/templates/starter/essentials/markdown.mdx +1 -0
- package/templates/starter/essentials/navigation.mdx +1 -0
- package/templates/starter/essentials/settings.mdx +1 -0
- package/templates/starter/index.mdx +1 -0
- package/templates/starter/quickstart.mdx +1 -0
package/schema/velu.schema.json
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"properties": {
|
|
10
10
|
"theme": {
|
|
11
11
|
"type": "string",
|
|
12
|
-
"enum": ["velu", "vepa"],
|
|
13
|
-
"description": "Visual preset. Vepa follows
|
|
12
|
+
"enum": ["velu", "vepa", "thulir", "aalam"],
|
|
13
|
+
"description": "Visual preset. Vepa follows Mintlify Sequoia, Thulir follows Mintlify Mint, and Aalam follows Mintlify Maple; light/dark preference is independent. Defaults to velu."
|
|
14
14
|
},
|
|
15
15
|
"$schema": {
|
|
16
16
|
"type": "string",
|
|
@@ -283,14 +283,34 @@
|
|
|
283
283
|
},
|
|
284
284
|
"pages": {
|
|
285
285
|
"type": "array",
|
|
286
|
-
"description": "Ordered list of page paths (no extension, relative to the project root) and/or nested groups.",
|
|
286
|
+
"description": "Ordered list of page paths (no extension, relative to the project root), page objects with optional icons, and/or nested groups.",
|
|
287
287
|
"items": {
|
|
288
288
|
"oneOf": [
|
|
289
289
|
{ "type": "string" },
|
|
290
|
+
{ "$ref": "#/$defs/pageEntry" },
|
|
290
291
|
{ "$ref": "#/$defs/group" }
|
|
291
292
|
]
|
|
292
293
|
}
|
|
293
294
|
},
|
|
295
|
+
"pageEntry": {
|
|
296
|
+
"type": "object",
|
|
297
|
+
"additionalProperties": false,
|
|
298
|
+
"required": ["page"],
|
|
299
|
+
"properties": {
|
|
300
|
+
"page": {
|
|
301
|
+
"type": "string",
|
|
302
|
+
"description": "Page path without extension, relative to the project root."
|
|
303
|
+
},
|
|
304
|
+
"icon": {
|
|
305
|
+
"type": "string",
|
|
306
|
+
"description": "Lucide icon id shown beside the page in the sidebar."
|
|
307
|
+
},
|
|
308
|
+
"title": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"description": "Optional sidebar label override (otherwise frontmatter title)."
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
294
314
|
"group": {
|
|
295
315
|
"type": "object",
|
|
296
316
|
"additionalProperties": false,
|
package/src/navigation.js
CHANGED
|
@@ -187,12 +187,20 @@ function normalizeGroup(g) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/** A `pages[]` entry is a string (page path), a generated OpenAPI page
|
|
190
|
-
* ({ apiPage, label, method }),
|
|
190
|
+
* ({ apiPage, label, method }), a page object ({ page, icon? }), or a
|
|
191
|
+
* nested group. Page icons also come from MDX frontmatter `icon`. */
|
|
191
192
|
function normalizePageEntry(entry) {
|
|
192
193
|
if (typeof entry === 'string') return { kind: 'page', pagePath: entry };
|
|
193
194
|
if (entry && entry.apiPage) {
|
|
194
195
|
return { kind: 'page', pagePath: entry.apiPage, label: entry.label, method: entry.method, api: true };
|
|
195
196
|
}
|
|
197
|
+
// Mintlify / Velu page object: { page: "path", icon?: "rocket", ... }
|
|
198
|
+
if (entry && typeof entry.page === 'string') {
|
|
199
|
+
const node = { kind: 'page', pagePath: entry.page };
|
|
200
|
+
if (entry.icon) node.icon = entry.icon;
|
|
201
|
+
if (entry.title || entry.label) node.label = entry.title ?? entry.label;
|
|
202
|
+
return node;
|
|
203
|
+
}
|
|
196
204
|
return normalizeGroup(entry);
|
|
197
205
|
}
|
|
198
206
|
|
|
@@ -392,13 +400,18 @@ function sidebarSectionsHaveApi(sections) {
|
|
|
392
400
|
}
|
|
393
401
|
|
|
394
402
|
/** Flatten sidebar sections (incl. nested groups) to an ordered list of
|
|
395
|
-
* page links {label, href} — the page reading order for
|
|
403
|
+
* page links {label, href, description?} — the page reading order for
|
|
404
|
+
* prev/next. */
|
|
396
405
|
function flattenSidebar(sections) {
|
|
397
406
|
const out = [];
|
|
398
407
|
const walk = (items) => {
|
|
399
408
|
for (const it of items) {
|
|
400
409
|
if (it.items) walk(it.items);
|
|
401
|
-
else
|
|
410
|
+
else {
|
|
411
|
+
const page = { label: it.label, href: it.href };
|
|
412
|
+
if (it.description) page.description = it.description;
|
|
413
|
+
out.push(page);
|
|
414
|
+
}
|
|
402
415
|
}
|
|
403
416
|
};
|
|
404
417
|
for (const s of sections) walk(s.items);
|
|
@@ -454,6 +467,12 @@ function itemFor(pageNode, pagesMap, ctx) {
|
|
|
454
467
|
// method badge); normal pages source their label from frontmatter.
|
|
455
468
|
const label = pageNode.label ?? fm?.sidebarTitle ?? fm?.title ?? titleCase(lastSeg(href));
|
|
456
469
|
const item = { label, href };
|
|
470
|
+
const icon = pageNode.icon ?? fm?.icon;
|
|
471
|
+
if (icon) item.icon = icon;
|
|
472
|
+
const description = fm?.description;
|
|
473
|
+
if (typeof description === 'string' && description.trim()) {
|
|
474
|
+
item.description = description.trim();
|
|
475
|
+
}
|
|
457
476
|
const method = pageNode.method ?? pagesMap[href]?.operation?.method;
|
|
458
477
|
if (method) item.method = method;
|
|
459
478
|
return item;
|
package/src/runtime/App.jsx
CHANGED
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
NotFound,
|
|
60
60
|
VeluMark,
|
|
61
61
|
} from 'velu-ui';
|
|
62
|
-
import { X, ChevronDown
|
|
62
|
+
import { X, ChevronDown } from 'lucide-react';
|
|
63
63
|
import ErrorBoundary from './ErrorBoundary.jsx';
|
|
64
64
|
|
|
65
65
|
// True under `velu dev`, false in the deployed production build (Vite injects
|
|
@@ -894,6 +894,7 @@ function DocsPage() {
|
|
|
894
894
|
<NavSelect
|
|
895
895
|
value={nav.activeProduct}
|
|
896
896
|
options={productOptions}
|
|
897
|
+
icon={productOptions.find((p) => p.active)?.icon}
|
|
897
898
|
linkComponent={RouterLink}
|
|
898
899
|
ariaLabel="Product"
|
|
899
900
|
component="nav-dropdown-products-selector"
|
|
@@ -928,7 +929,7 @@ function DocsPage() {
|
|
|
928
929
|
// against it like any other heading.
|
|
929
930
|
const pageId = React.useMemo(() => {
|
|
930
931
|
if (!frontmatter.title) return null;
|
|
931
|
-
if (site.theme === 'vepa') return 'page-title';
|
|
932
|
+
if (site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme)) return 'page-title';
|
|
932
933
|
return new GithubSlugger().slug(frontmatter.title);
|
|
933
934
|
}, [frontmatter.title, site.theme]);
|
|
934
935
|
|
|
@@ -962,6 +963,49 @@ function DocsPage() {
|
|
|
962
963
|
// of sidebarOpen so neither breakpoint's default leaks into the
|
|
963
964
|
// other.
|
|
964
965
|
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
966
|
+
const drawerRef = React.useRef(null);
|
|
967
|
+
const [drawerViewport, setDrawerViewport] = React.useState(false);
|
|
968
|
+
React.useEffect(() => {
|
|
969
|
+
const root = drawerRef.current?.parentElement;
|
|
970
|
+
if (!root) return;
|
|
971
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
972
|
+
const narrow = entry.contentRect.width <= (['thulir', 'aalam'].includes(site.theme) ? 1023 : 640);
|
|
973
|
+
setDrawerViewport(narrow);
|
|
974
|
+
if (!narrow) setDrawerOpen(false);
|
|
975
|
+
});
|
|
976
|
+
observer.observe(root);
|
|
977
|
+
return () => observer.disconnect();
|
|
978
|
+
}, [site.theme]);
|
|
979
|
+
React.useEffect(() => {
|
|
980
|
+
if (!drawerOpen || !drawerViewport) return;
|
|
981
|
+
const drawer = drawerRef.current;
|
|
982
|
+
const previousFocus = document.activeElement;
|
|
983
|
+
const previousOverflow = document.body.style.overflow;
|
|
984
|
+
document.body.style.overflow = 'hidden';
|
|
985
|
+
const siblings = [...drawer.parentElement.children].filter(e => e !== drawer && !e.classList.contains('velu-docs-layout__scrim'));
|
|
986
|
+
const inertStates = siblings.map(e => e.inert);
|
|
987
|
+
siblings.forEach(e => { e.inert = true; });
|
|
988
|
+
drawer.querySelector('.velu-docs-layout__drawer-close')?.focus();
|
|
989
|
+
const onKey = e => {
|
|
990
|
+
if (e.key === 'Escape') {
|
|
991
|
+
if (drawer.querySelector('.velu-docs-layout__drawer-docselect[data-open="true"]')) return;
|
|
992
|
+
setDrawerOpen(false); return;
|
|
993
|
+
}
|
|
994
|
+
if (e.key !== 'Tab') return;
|
|
995
|
+
const items = [...drawer.querySelectorAll('a[href],button,input,select,textarea,[tabindex]')].filter(el => !el.disabled && el.tabIndex >= 0 && el.getClientRects().length && getComputedStyle(el).visibility !== 'hidden');
|
|
996
|
+
const first = items[0], last = items.at(-1);
|
|
997
|
+
if (!first) { e.preventDefault(); return; }
|
|
998
|
+
if (e.shiftKey && (document.activeElement === first || !drawer.contains(document.activeElement))) { e.preventDefault(); last.focus(); }
|
|
999
|
+
else if (!e.shiftKey && (document.activeElement === last || !drawer.contains(document.activeElement))) { e.preventDefault(); first.focus(); }
|
|
1000
|
+
};
|
|
1001
|
+
document.addEventListener('keydown', onKey);
|
|
1002
|
+
return () => {
|
|
1003
|
+
document.removeEventListener('keydown', onKey);
|
|
1004
|
+
document.body.style.overflow = previousOverflow;
|
|
1005
|
+
siblings.forEach((e,i) => { e.inert = inertStates[i]; });
|
|
1006
|
+
if (previousFocus?.isConnected) previousFocus.focus({preventScroll:true});
|
|
1007
|
+
};
|
|
1008
|
+
}, [drawerOpen, drawerViewport]);
|
|
965
1009
|
|
|
966
1010
|
// Drawer's nav dropdown — list of section links (Home/Docs/Blog),
|
|
967
1011
|
// mirrors the desktop tabs. Click-outside + Escape close.
|
|
@@ -973,7 +1017,7 @@ function DocsPage() {
|
|
|
973
1017
|
if (!navRef.current?.contains(e.target)) setNavOpen(false);
|
|
974
1018
|
};
|
|
975
1019
|
const onKey = (e) => {
|
|
976
|
-
if (e.key === 'Escape') setNavOpen(false);
|
|
1020
|
+
if (e.key === 'Escape') { setNavOpen(false); navRef.current?.querySelector('button')?.focus(); }
|
|
977
1021
|
};
|
|
978
1022
|
document.addEventListener('mousedown', onDocClick);
|
|
979
1023
|
document.addEventListener('keydown', onKey);
|
|
@@ -1072,40 +1116,30 @@ function DocsPage() {
|
|
|
1072
1116
|
// a gap above the full-page Ask AI canvas.
|
|
1073
1117
|
}, [chrome.mode]);
|
|
1074
1118
|
|
|
1075
|
-
// The
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
//
|
|
1079
|
-
// re-render). Re-binds on chatOpen toggle (the right TOC
|
|
1080
|
-
// mounts/unmounts with it).
|
|
1119
|
+
// The right TOC scrolls independently of the page. On scroll/resize
|
|
1120
|
+
// we set data-fade-top/-bottom so CSS can fade its edges only when
|
|
1121
|
+
// there's content beyond them. Written imperatively (no re-render).
|
|
1122
|
+
// Re-binds on chatOpen toggle (the right TOC mounts/unmounts with it).
|
|
1081
1123
|
const leftAsideRef = React.useRef(null);
|
|
1082
1124
|
const rightAsideRef = React.useRef(null);
|
|
1083
1125
|
React.useEffect(() => {
|
|
1084
|
-
const
|
|
1085
|
-
if (!
|
|
1126
|
+
const el = rightAsideRef.current;
|
|
1127
|
+
if (!el) return undefined;
|
|
1086
1128
|
const update = () => {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
el.
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
: 'false';
|
|
1093
|
-
}
|
|
1129
|
+
el.dataset.fadeTop = el.scrollTop > 0 ? 'true' : 'false';
|
|
1130
|
+
el.dataset.fadeBottom =
|
|
1131
|
+
el.scrollTop + el.clientHeight < el.scrollHeight - 1
|
|
1132
|
+
? 'true'
|
|
1133
|
+
: 'false';
|
|
1094
1134
|
};
|
|
1095
1135
|
update();
|
|
1096
|
-
// Recompute on scroll AND on any size change of the scroll region
|
|
1097
|
-
// or its content (ResizeObserver) + window resize — so the fade
|
|
1098
|
-
// reflects hidden content persistently, not just during a scroll
|
|
1099
|
-
// gesture (e.g. after a route change shifts the nav's height).
|
|
1100
1136
|
const ro = new ResizeObserver(update);
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
if (el.firstElementChild) ro.observe(el.firstElementChild);
|
|
1105
|
-
});
|
|
1137
|
+
el.addEventListener('scroll', update, { passive: true });
|
|
1138
|
+
ro.observe(el);
|
|
1139
|
+
if (el.firstElementChild) ro.observe(el.firstElementChild);
|
|
1106
1140
|
window.addEventListener('resize', update);
|
|
1107
1141
|
return () => {
|
|
1108
|
-
|
|
1142
|
+
el.removeEventListener('scroll', update);
|
|
1109
1143
|
ro.disconnect();
|
|
1110
1144
|
window.removeEventListener('resize', update);
|
|
1111
1145
|
};
|
|
@@ -1113,9 +1147,8 @@ function DocsPage() {
|
|
|
1113
1147
|
|
|
1114
1148
|
// Flag the sidebar section heading currently pinned at the top of the
|
|
1115
1149
|
// scroll region (CSS sticky gives no "is-stuck" hook). When the pinned
|
|
1116
|
-
// heading changes as you scroll, the new one gets data-stuck
|
|
1117
|
-
//
|
|
1118
|
-
// section set changes with it.
|
|
1150
|
+
// heading changes as you scroll, the new one gets data-stuck.
|
|
1151
|
+
// Re-binds on page/tab change since the section set changes with it.
|
|
1119
1152
|
React.useEffect(() => {
|
|
1120
1153
|
const scroller = leftAsideRef.current;
|
|
1121
1154
|
if (!scroller) return;
|
|
@@ -1198,8 +1231,8 @@ function DocsPage() {
|
|
|
1198
1231
|
// Callback ref so this rebinds when the footer mounts/unmounts.
|
|
1199
1232
|
// Custom and assistant unmount the footer; a mount-once listener
|
|
1200
1233
|
// would keep reading the detached node (getBoundingClientRect top
|
|
1201
|
-
// is 0)
|
|
1202
|
-
//
|
|
1234
|
+
// is 0). footerOverlap still pads the right TOC so the last items
|
|
1235
|
+
// can scroll clear of the footer as it rises under content + TOC.
|
|
1203
1236
|
const [footerEl, setFooterEl] = React.useState(null);
|
|
1204
1237
|
const [footerOverlap, setFooterOverlap] = React.useState(0);
|
|
1205
1238
|
React.useEffect(() => {
|
|
@@ -1349,17 +1382,19 @@ function DocsPage() {
|
|
|
1349
1382
|
</Cluster>
|
|
1350
1383
|
}
|
|
1351
1384
|
actions={navActions}
|
|
1352
|
-
trailing={<ThemeToggle variant={site.theme === 'vepa' ? 'menu' : undefined} />}
|
|
1385
|
+
trailing={<ThemeToggle variant={site.theme === 'aalam' ? 'inline' : site.theme === 'vepa' || site.theme === 'thulir' ? 'menu' : undefined} />}
|
|
1353
1386
|
tabsTrailing={
|
|
1354
1387
|
chrome.mode === 'custom' || chrome.mode === 'assistant'
|
|
1355
1388
|
? undefined
|
|
1356
1389
|
: languageSwitcher || undefined
|
|
1357
1390
|
}
|
|
1358
1391
|
onMenuClick={chrome.sidebar ? () => setDrawerOpen((v) => !v) : undefined}
|
|
1392
|
+
menuOpen={drawerOpen}
|
|
1393
|
+
menuId={site.theme === 'vepa' ? 'sidebar-content' : 'sidebar'}
|
|
1359
1394
|
breadcrumb={
|
|
1360
1395
|
chrome.mode === 'custom' || chrome.mode === 'assistant'
|
|
1361
1396
|
? []
|
|
1362
|
-
: (nav?.breadcrumb ?? [])
|
|
1397
|
+
: ['thulir', 'aalam'].includes(site.theme) ? (nav?.breadcrumb ?? []).slice(-2) : (nav?.breadcrumb ?? [])
|
|
1363
1398
|
}
|
|
1364
1399
|
activeTab={nav?.activeTab}
|
|
1365
1400
|
tabs={
|
|
@@ -1369,23 +1404,19 @@ function DocsPage() {
|
|
|
1369
1404
|
}
|
|
1370
1405
|
/>
|
|
1371
1406
|
|
|
1372
|
-
{/* Fixed left sidebar — pinned to viewport-left
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
as plain `top`). */}
|
|
1407
|
+
{/* Fixed left sidebar — pinned to viewport-left, full height.
|
|
1408
|
+
The site footer is indented under content + TOC only, so the
|
|
1409
|
+
rail never lifts for the footer. `top` + `bottom` give a
|
|
1410
|
+
robust height (some browsers don't honour `inset-block-start`
|
|
1411
|
+
for fixed positioning the same way as plain `top`). */}
|
|
1378
1412
|
<aside
|
|
1413
|
+
ref={drawerRef}
|
|
1414
|
+
role={drawerViewport ? 'dialog' : undefined}
|
|
1415
|
+
aria-modal={drawerViewport && drawerOpen ? true : undefined}
|
|
1416
|
+
aria-label={drawerViewport ? 'Navigation' : undefined}
|
|
1417
|
+
inert={drawerViewport && !drawerOpen ? '' : undefined}
|
|
1379
1418
|
id={site.theme === 'vepa' ? 'sidebar-content' : 'sidebar'}
|
|
1380
1419
|
className="velu-docs-layout__aside velu-docs-layout__aside--left"
|
|
1381
|
-
style={{
|
|
1382
|
-
/* Bottom edge stays a fixed gap above the viewport bottom,
|
|
1383
|
-
AND lifts to keep that gap above the footer as it scrolls
|
|
1384
|
-
into view (footerOverlap = how far the footer intrudes).
|
|
1385
|
-
Set as a custom prop so the mobile drawer's
|
|
1386
|
-
`inset-block-end: 0` override still wins. */
|
|
1387
|
-
'--velu-aside-bottom': `calc(${footerOverlap}px + var(--s4))`,
|
|
1388
|
-
}}
|
|
1389
1420
|
// Close the mobile drawer as soon as a nav link is activated.
|
|
1390
1421
|
// The pathname effect below also closes it on route change; this
|
|
1391
1422
|
// covers same-route hash jumps and feels snappier on touch devices.
|
|
@@ -1413,11 +1444,11 @@ function DocsPage() {
|
|
|
1413
1444
|
) : (
|
|
1414
1445
|
<>
|
|
1415
1446
|
<VeluMark />
|
|
1416
|
-
<span className="velu-header__wordmark">{site.name}</span>
|
|
1447
|
+
<span className="velu-header__wordmark" title={site.name}>{site.name}</span>
|
|
1417
1448
|
</>
|
|
1418
1449
|
)}
|
|
1419
1450
|
</RouterLink>
|
|
1420
|
-
<ThemeToggle />
|
|
1451
|
+
<ThemeToggle variant={site.theme === 'vepa' || site.theme === 'thulir' ? 'menu' : undefined} />
|
|
1421
1452
|
<button
|
|
1422
1453
|
type="button"
|
|
1423
1454
|
className="velu-docs-layout__drawer-close"
|
|
@@ -1427,21 +1458,45 @@ function DocsPage() {
|
|
|
1427
1458
|
<X aria-hidden="true" focusable="false" />
|
|
1428
1459
|
</button>
|
|
1429
1460
|
</div>
|
|
1461
|
+
{site.theme === 'aalam' && productSwitcher && (
|
|
1462
|
+
<div className="velu-docs-aside-product">{productSwitcher}</div>
|
|
1463
|
+
)}
|
|
1464
|
+
{site.theme === 'aalam' && (
|
|
1465
|
+
<div className="velu-docs-aside-search">
|
|
1466
|
+
<Search
|
|
1467
|
+
id="search-bar-entry-sidebar"
|
|
1468
|
+
unavailable={IS_DEV_PREVIEW}
|
|
1469
|
+
search={IS_DEV_PREVIEW ? undefined : searchDocs}
|
|
1470
|
+
onSelect={(item) => item.href && navigate(item.href)}
|
|
1471
|
+
/>
|
|
1472
|
+
</div>
|
|
1473
|
+
)}
|
|
1430
1474
|
{/* Nav dropdown — custom button + menu, fills drawer width.
|
|
1431
1475
|
Items mirror the desktop tabs (Home / Docs / Blog).
|
|
1432
1476
|
Click-outside + Escape close (see navOpen useEffect
|
|
1433
|
-
above). Chevron rotates 180° on open.
|
|
1477
|
+
above). Chevron rotates 180° on open. Shown only when
|
|
1478
|
+
there is more than one tab to switch between. */}
|
|
1479
|
+
{navItems.length > 1 && (
|
|
1434
1480
|
<div
|
|
1435
1481
|
ref={navRef}
|
|
1436
1482
|
className="velu-docs-layout__drawer-docselect"
|
|
1437
1483
|
data-open={navOpen ? 'true' : 'false'}
|
|
1438
1484
|
>
|
|
1485
|
+
<label htmlFor="drawer-section-picker">Documentation section</label>
|
|
1439
1486
|
<button
|
|
1487
|
+
id="drawer-section-picker"
|
|
1440
1488
|
type="button"
|
|
1441
1489
|
className="velu-docs-layout__drawer-docselect-btn"
|
|
1442
1490
|
onClick={() => setNavOpen((o) => !o)}
|
|
1443
1491
|
aria-haspopup="menu"
|
|
1444
1492
|
aria-expanded={navOpen}
|
|
1493
|
+
aria-controls="drawer-section-options"
|
|
1494
|
+
onKeyDown={(e) => {
|
|
1495
|
+
if (e.key === 'ArrowDown') {
|
|
1496
|
+
e.preventDefault(); setNavOpen(true);
|
|
1497
|
+
requestAnimationFrame(() => navRef.current?.querySelector('[role="menuitem"]')?.focus());
|
|
1498
|
+
}
|
|
1499
|
+
}}
|
|
1445
1500
|
>
|
|
1446
1501
|
<span className="velu-docs-layout__drawer-docselect-label">
|
|
1447
1502
|
{activeNavLabel}
|
|
@@ -1453,9 +1508,16 @@ function DocsPage() {
|
|
|
1453
1508
|
/>
|
|
1454
1509
|
</button>
|
|
1455
1510
|
<ul
|
|
1511
|
+
id="drawer-section-options"
|
|
1456
1512
|
className="velu-docs-layout__drawer-docselect-menu"
|
|
1457
1513
|
role="menu"
|
|
1458
1514
|
aria-hidden={!navOpen}
|
|
1515
|
+
onKeyDown={(e) => {
|
|
1516
|
+
const items = [...e.currentTarget.querySelectorAll('[role="menuitem"]')];
|
|
1517
|
+
const index = items.indexOf(document.activeElement);
|
|
1518
|
+
const next = e.key === 'ArrowDown' ? (index + 1) % items.length : e.key === 'ArrowUp' ? (index + items.length - 1) % items.length : e.key === 'Home' ? 0 : e.key === 'End' ? items.length - 1 : null;
|
|
1519
|
+
if (next !== null) { e.preventDefault(); items[next]?.focus(); }
|
|
1520
|
+
}}
|
|
1459
1521
|
>
|
|
1460
1522
|
{navItems.map((it) => (
|
|
1461
1523
|
<li key={it.href} role="none">
|
|
@@ -1474,24 +1536,25 @@ function DocsPage() {
|
|
|
1474
1536
|
))}
|
|
1475
1537
|
</ul>
|
|
1476
1538
|
</div>
|
|
1539
|
+
)}
|
|
1477
1540
|
{/* Context zone — product switcher + anchors at the top of
|
|
1478
1541
|
the sidebar (all breakpoints), plus version/language
|
|
1479
1542
|
switchers that only show on mobile (desktop has them in
|
|
1480
|
-
the header).
|
|
1481
|
-
|
|
1482
|
-
{(productSwitcher ||
|
|
1483
|
-
versionSwitcher ||
|
|
1543
|
+
the header). Aalam (Maple) puts product above search and
|
|
1544
|
+
pins version to the sidebar footer instead. */}
|
|
1545
|
+
{((productSwitcher && site.theme !== 'aalam') ||
|
|
1546
|
+
(versionSwitcher && site.theme !== 'aalam') ||
|
|
1484
1547
|
languageSwitcher ||
|
|
1485
|
-
anchors.length > 0) && (
|
|
1548
|
+
(anchors.length > 0 && site.theme !== 'aalam')) && (
|
|
1486
1549
|
<Stack space="var(--s-1)" className="velu-docs-context">
|
|
1487
|
-
{productSwitcher}
|
|
1488
|
-
{versionSwitcher && (
|
|
1550
|
+
{site.theme !== 'aalam' && productSwitcher}
|
|
1551
|
+
{versionSwitcher && site.theme !== 'aalam' && (
|
|
1489
1552
|
<span className="velu-show-on-mobile">{versionSwitcher}</span>
|
|
1490
1553
|
)}
|
|
1491
1554
|
{languageSwitcher && (
|
|
1492
1555
|
<span className="velu-show-on-mobile">{languageSwitcher}</span>
|
|
1493
1556
|
)}
|
|
1494
|
-
{anchors.length > 0 && (
|
|
1557
|
+
{anchors.length > 0 && site.theme !== 'aalam' && (
|
|
1495
1558
|
<ul className="velu-docs-anchors" data-component="nav-anchors">
|
|
1496
1559
|
{anchors.map((a, i) => (
|
|
1497
1560
|
<li key={i}>
|
|
@@ -1520,26 +1583,50 @@ function DocsPage() {
|
|
|
1520
1583
|
)}
|
|
1521
1584
|
{/* Hairline separating the top anchor links from the nav sections
|
|
1522
1585
|
(faithful to the sidebar design's anchor↔sidebar divider). */}
|
|
1523
|
-
{anchors.length > 0 && (
|
|
1586
|
+
{anchors.length > 0 && site.theme !== 'aalam' && (
|
|
1524
1587
|
<sidebar-nav-group-divider class="velu-docs-context-divider" aria-hidden="true" />
|
|
1525
1588
|
)}
|
|
1526
1589
|
{/* Only this region scrolls — the context zone above stays
|
|
1527
|
-
pinned.
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
nav fully to that end. */}
|
|
1590
|
+
pinned. Active page links scroll into view on navigation
|
|
1591
|
+
(Sidebar / ApiSidebar). Aalam (Maple) scrolls the whole
|
|
1592
|
+
left column, including anchors, in one overflow. */}
|
|
1531
1593
|
<div className="velu-docs-nav-region">
|
|
1532
1594
|
<div
|
|
1533
|
-
id={site.theme === 'vepa' ? 'navigation-items' : 'sidebar-content'}
|
|
1595
|
+
id={site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme) ? 'navigation-items' : 'sidebar-content'}
|
|
1534
1596
|
ref={leftAsideRef}
|
|
1535
|
-
className=
|
|
1597
|
+
className={`velu-docs-nav-scroll${site.theme === 'aalam' ? '' : ' velu-hide-scrollbar'}`}
|
|
1536
1598
|
style={{
|
|
1537
|
-
/*
|
|
1538
|
-
|
|
1539
|
-
|
|
1599
|
+
/* scroll-padding only — keep content clear of sticky
|
|
1600
|
+
headings / footer without shortening the scrollbar. */
|
|
1601
|
+
scrollPaddingBlockStart: '2.75rem',
|
|
1540
1602
|
scrollPaddingBlockEnd: 'var(--s1)',
|
|
1541
1603
|
}}
|
|
1542
1604
|
>
|
|
1605
|
+
{site.theme === 'aalam' && anchors.length > 0 && (
|
|
1606
|
+
<ul className="velu-docs-anchors" data-component="nav-anchors">
|
|
1607
|
+
{anchors.map((a, i) => (
|
|
1608
|
+
<li key={i}>
|
|
1609
|
+
<a
|
|
1610
|
+
data-component="nav-anchor"
|
|
1611
|
+
className="velu-docs-anchors__link"
|
|
1612
|
+
href={a.href}
|
|
1613
|
+
target="_blank"
|
|
1614
|
+
rel="noreferrer"
|
|
1615
|
+
>
|
|
1616
|
+
{a.icon && (
|
|
1617
|
+
<span
|
|
1618
|
+
className="velu-docs-anchors__icon"
|
|
1619
|
+
aria-hidden="true"
|
|
1620
|
+
>
|
|
1621
|
+
{resolveIcon(a.icon, { size: 16 })}
|
|
1622
|
+
</span>
|
|
1623
|
+
)}
|
|
1624
|
+
<span>{a.label}</span>
|
|
1625
|
+
</a>
|
|
1626
|
+
</li>
|
|
1627
|
+
))}
|
|
1628
|
+
</ul>
|
|
1629
|
+
)}
|
|
1543
1630
|
{/* Sidebar STYLE is chosen per-tab, not per-page: if this tab
|
|
1544
1631
|
holds any API page, the whole tab uses the flat API sidebar
|
|
1545
1632
|
(so opening an intro page next to API operations doesn't flip
|
|
@@ -1562,31 +1649,11 @@ function DocsPage() {
|
|
|
1562
1649
|
/>
|
|
1563
1650
|
)}
|
|
1564
1651
|
</div>
|
|
1565
|
-
<button
|
|
1566
|
-
type="button"
|
|
1567
|
-
className="velu-docs-nav-arrow velu-docs-nav-arrow--up"
|
|
1568
|
-
aria-label="Scroll navigation to top"
|
|
1569
|
-
onClick={() =>
|
|
1570
|
-
leftAsideRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
|
|
1571
|
-
}
|
|
1572
|
-
>
|
|
1573
|
-
<ChevronUp aria-hidden="true" focusable="false" />
|
|
1574
|
-
</button>
|
|
1575
|
-
<button
|
|
1576
|
-
type="button"
|
|
1577
|
-
className="velu-docs-nav-arrow velu-docs-nav-arrow--down"
|
|
1578
|
-
aria-label="Scroll navigation to bottom"
|
|
1579
|
-
onClick={() =>
|
|
1580
|
-
leftAsideRef.current?.scrollTo({
|
|
1581
|
-
top: leftAsideRef.current.scrollHeight,
|
|
1582
|
-
behavior: 'smooth',
|
|
1583
|
-
})
|
|
1584
|
-
}
|
|
1585
|
-
>
|
|
1586
|
-
<ChevronDown aria-hidden="true" focusable="false" />
|
|
1587
|
-
</button>
|
|
1588
1652
|
</div>
|
|
1589
1653
|
</Stack>
|
|
1654
|
+
{site.theme === 'aalam' && versionSwitcher && (
|
|
1655
|
+
<div className="velu-docs-aside-footer">{versionSwitcher}</div>
|
|
1656
|
+
)}
|
|
1590
1657
|
</aside>
|
|
1591
1658
|
|
|
1592
1659
|
{/* No separate rail element — at narrow widths the sidebar's
|
|
@@ -1621,10 +1688,17 @@ function DocsPage() {
|
|
|
1621
1688
|
samples={entry.samples}
|
|
1622
1689
|
responses={entry.operation.responses}
|
|
1623
1690
|
/>
|
|
1624
|
-
) : isChangelog && changelogTags.length ? (
|
|
1691
|
+
) : isChangelog && changelogTags.length && site.theme !== 'vepa' ? (
|
|
1625
1692
|
<ChangelogFilters tags={changelogTags} />
|
|
1693
|
+
) : site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme) ? (
|
|
1694
|
+
<>
|
|
1695
|
+
{isChangelog && changelogTags.length > 0 ? (
|
|
1696
|
+
<ChangelogFilters tags={changelogTags} />
|
|
1697
|
+
) : null}
|
|
1698
|
+
<VepaToc items={pageToc} activeId={['thulir', 'aalam'].includes(site.theme) && activeId === pageId ? pageToc[0]?.id : activeId} onSelect={scrollTo} />
|
|
1699
|
+
</>
|
|
1626
1700
|
) : (
|
|
1627
|
-
|
|
1701
|
+
<Toc id="table-of-contents-content" items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1628
1702
|
)}
|
|
1629
1703
|
</aside>
|
|
1630
1704
|
)}
|
|
@@ -1666,7 +1740,7 @@ function DocsPage() {
|
|
|
1666
1740
|
on tab/mobile we keep the normal TOC bar (no filters). Hidden
|
|
1667
1741
|
entirely when the page mode has no table of contents. */}
|
|
1668
1742
|
{chrome.toc && (
|
|
1669
|
-
<TocBar items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1743
|
+
<TocBar items={toc} activeId={activeId} onSelect={scrollTo} label={['thulir', 'aalam'].includes(site.theme) ? 'On this page' : undefined} />
|
|
1670
1744
|
)}
|
|
1671
1745
|
<main
|
|
1672
1746
|
id="content-area"
|
|
@@ -1706,7 +1780,7 @@ function DocsPage() {
|
|
|
1706
1780
|
</span>
|
|
1707
1781
|
</button>
|
|
1708
1782
|
)}
|
|
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 : ""}>
|
|
1783
|
+
<div id={site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme) ? undefined : 'content'} data-contextual-actions={site.contextual?.options?.length ? 'true' : 'false'} className="velu-docs-layout__article" data-pagefind-body={chrome.assistant ? undefined : ""}>
|
|
1710
1784
|
{/* Per-page agent/IDE action bar: the section eyebrow + a
|
|
1711
1785
|
"Copy Page" split-button whose dropdown is driven by the
|
|
1712
1786
|
Mintlify-compatible `contextual` config. Renders nothing
|
|
@@ -1736,7 +1810,7 @@ function DocsPage() {
|
|
|
1736
1810
|
Custom / frame / assistant are a blank canvas — no auto hero. */}
|
|
1737
1811
|
{chrome.hero && (frontmatter.title || frontmatter.description) && (
|
|
1738
1812
|
<div className="velu-hero">
|
|
1739
|
-
{frontmatter.title && <h1 id={site.theme === 'vepa' ? 'page-title' : pageId}>{frontmatter.title}</h1>}
|
|
1813
|
+
{frontmatter.title && <h1 id={site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme) ? 'page-title' : pageId}>{frontmatter.title}</h1>}
|
|
1740
1814
|
{frontmatter.description && (
|
|
1741
1815
|
<p
|
|
1742
1816
|
style={{
|
|
@@ -1757,7 +1831,7 @@ function DocsPage() {
|
|
|
1757
1831
|
level. Falls back to a not-found / missing-file notice
|
|
1758
1832
|
when the route has no page (or its file is absent). */}
|
|
1759
1833
|
<MDXProvider components={defaultMdxComponents}>
|
|
1760
|
-
<mdx-content key={site.theme === 'vepa' ? pathname : undefined} id={site.theme === 'vepa' && !entry?.api ? 'content' : undefined} class="velu-prose">
|
|
1834
|
+
<mdx-content key={site.theme === 'vepa' ? pathname : undefined} id={(site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme)) && !entry?.api ? 'content' : undefined} class="velu-prose">
|
|
1761
1835
|
{PageComponent ? (
|
|
1762
1836
|
<ErrorBoundary key={pathname} file={entry?.relPath}>
|
|
1763
1837
|
{entry?.mdxApi ? <ApiReferencePage {...entry.mdxApi} preset={site.theme}><PageComponent /></ApiReferencePage> : <PageComponent />}
|
|
@@ -1773,7 +1847,7 @@ function DocsPage() {
|
|
|
1773
1847
|
</MDXProvider>
|
|
1774
1848
|
{/* Page-foot feedback widget — ref'd so the sticky AskBar
|
|
1775
1849
|
above can hide as the user scrolls near it. */}
|
|
1776
|
-
{chrome.articleChrome && (
|
|
1850
|
+
{chrome.articleChrome && !['thulir', 'aalam'].includes(site.theme) && (
|
|
1777
1851
|
<div ref={setFeedbackEl} style={{ marginTop: 'var(--s3)' }}>
|
|
1778
1852
|
{/* key resets the widget's vote state on page change. "Yes"
|
|
1779
1853
|
submits immediately; "No" submits once its form is sent.
|
|
@@ -1794,7 +1868,8 @@ function DocsPage() {
|
|
|
1794
1868
|
{chrome.articleChrome && !frontmatter.hidePagination && (nav?.prev || nav?.next) && (
|
|
1795
1869
|
<PageNav
|
|
1796
1870
|
id="pagination"
|
|
1797
|
-
style={{ marginTop: 'var(--s2)' }}
|
|
1871
|
+
style={['thulir', 'aalam'].includes(site.theme) ? undefined : { marginTop: 'var(--s2)' }}
|
|
1872
|
+
variant={site.theme === 'aalam' ? 'aalam' : 'default'}
|
|
1798
1873
|
prev={nav?.prev}
|
|
1799
1874
|
next={nav?.next}
|
|
1800
1875
|
linkComponent={RouterLink}
|
|
@@ -1834,7 +1909,7 @@ function DocsPage() {
|
|
|
1834
1909
|
full footer section); with link columns they live in the full
|
|
1835
1910
|
footer below instead. */}
|
|
1836
1911
|
{chrome.articleChrome && (footerHasLinks ? (
|
|
1837
|
-
site.theme === 'vepa' ? null : <PoweredBy />
|
|
1912
|
+
site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme) ? null : <PoweredBy />
|
|
1838
1913
|
) : (
|
|
1839
1914
|
<div className="velu-content-foot">
|
|
1840
1915
|
<SocialLinks socials={footerSocials} />
|
|
@@ -1847,13 +1922,13 @@ function DocsPage() {
|
|
|
1847
1922
|
)}
|
|
1848
1923
|
</div>
|
|
1849
1924
|
|
|
1850
|
-
{/* Full site footer — only when the config provides link columns.
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1925
|
+
{/* Full site footer — only when the config provides link columns.
|
|
1926
|
+
Indented under content + TOC (see docs-layout.css); the left
|
|
1927
|
+
rail stays full-height beside it. Right TOC pads for the
|
|
1928
|
+
overlap via footerOverlap below. */}
|
|
1854
1929
|
{footerHasLinks && chrome.footer && (
|
|
1855
1930
|
<advanced-footer
|
|
1856
|
-
id={site.theme === 'vepa' ? undefined : 'footer'}
|
|
1931
|
+
id={site.theme === 'vepa' || ['thulir', 'aalam'].includes(site.theme) ? undefined : 'footer'}
|
|
1857
1932
|
ref={setFooterEl}
|
|
1858
1933
|
data-velu-footer
|
|
1859
1934
|
style={{ position: 'relative', zIndex: 20 }}
|