@veluai/velu 0.2.35 → 0.2.36

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.
Files changed (45) hide show
  1. package/README.md +39 -0
  2. package/dist/cli.js +57 -49
  3. package/package.json +1 -1
  4. package/runtime/velu-ui/components/Accordion.jsx +3 -3
  5. package/runtime/velu-ui/components/ApiClient.jsx +4 -2
  6. package/runtime/velu-ui/components/ApiReferencePage.jsx +12 -12
  7. package/runtime/velu-ui/components/ApiSamples.jsx +2 -2
  8. package/runtime/velu-ui/components/AskBar.jsx +3 -2
  9. package/runtime/velu-ui/components/Callout.jsx +11 -26
  10. package/runtime/velu-ui/components/Card.jsx +9 -8
  11. package/runtime/velu-ui/components/ChangelogFilters.jsx +2 -1
  12. package/runtime/velu-ui/components/Chatbot.jsx +11 -5
  13. package/runtime/velu-ui/components/CodeBlock.jsx +7 -6
  14. package/runtime/velu-ui/components/Columns.jsx +1 -0
  15. package/runtime/velu-ui/components/ContextMenu.jsx +3 -2
  16. package/runtime/velu-ui/components/Field.jsx +2 -2
  17. package/runtime/velu-ui/components/Icon.jsx +1 -0
  18. package/runtime/velu-ui/components/Image.jsx +2 -2
  19. package/runtime/velu-ui/components/MethodBadge.jsx +2 -2
  20. package/runtime/velu-ui/components/NavSelect.jsx +22 -4
  21. package/runtime/velu-ui/components/NotFound.jsx +7 -7
  22. package/runtime/velu-ui/components/PageFeedback.jsx +13 -3
  23. package/runtime/velu-ui/components/PageHeader.jsx +24 -9
  24. package/runtime/velu-ui/components/PageNav.jsx +2 -1
  25. package/runtime/velu-ui/components/Prompt.jsx +2 -2
  26. package/runtime/velu-ui/components/Search.jsx +1 -0
  27. package/runtime/velu-ui/components/Sidebar.jsx +18 -6
  28. package/runtime/velu-ui/components/Steps.jsx +4 -4
  29. package/runtime/velu-ui/components/ThemeToggle.jsx +1 -0
  30. package/runtime/velu-ui/components/Toc.jsx +6 -4
  31. package/runtime/velu-ui/components/Tree.jsx +6 -5
  32. package/runtime/velu-ui/components/TryItBar.jsx +1 -0
  33. package/runtime/velu-ui/components/Update.jsx +2 -2
  34. package/runtime/velu-ui/components/callout.css +28 -0
  35. package/runtime/velu-ui/components/docs-layout.css +5 -0
  36. package/runtime/velu-ui/components/sidebar.css +5 -5
  37. package/runtime/velu-ui/element-selectors.css +66 -0
  38. package/runtime/velu-ui/primitives/Cluster.jsx +12 -0
  39. package/runtime/velu-ui/primitives/Stack.jsx +12 -0
  40. package/runtime/velu-ui/primitives/Switcher.jsx +11 -1
  41. package/runtime/velu-ui/styles.css +51 -35
  42. package/src/runtime/App.jsx +43 -11
  43. package/src/runtime/client-entry.jsx +23 -0
  44. package/src/runtime/dev-warnings.js +16 -0
  45. package/src/runtime/server-entry.jsx +1 -0
@@ -1,9 +1,18 @@
1
1
  /* Aggregate stylesheet for velu-ui. Consumers import this once.
2
2
 
3
- Tailwind v4 first (theme/preflight/utilities all in @layer).
4
- base.css is intentionally UNLAYERED, so it wins over Tailwind's
5
- preflight `base` layer our fluid type/measure beat Tailwind's reset
6
- by cascade-layer precedence (unlayered > layered). */
3
+ Cascade layers, lowest to highest precedence:
4
+ Tailwind's theme / base (preflight) / components / utilities
5
+ velu - everything in this package (base.css + every component sheet)
6
+ (unlayered) - the PROJECT's own custom CSS (any .css file in a docs
7
+ project is linked after this sheet, unlayered)
8
+ Unlayered styles beat every layer regardless of specificity, so a project
9
+ can restyle components with Mintlify-style element selectors - a plain
10
+ `card { border-radius: 0 }` (specificity 0,0,1) overrides `.velu-card`
11
+ (0,1,0) because velu-ui lives in a layer and the project sheet does not.
12
+ The `velu` layer is declared after Tailwind's, so our fluid type/measure
13
+ still beat Tailwind's preflight, as before. */
14
+ @layer theme, base, components, utilities, velu;
15
+
7
16
  @import 'tailwindcss';
8
17
 
9
18
  /* Map Tailwind's `dark:` variant onto our [data-theme="dark"] attribute
@@ -14,36 +23,43 @@
14
23
  and ignores node_modules — velu-ui is reached via a workspace symlink
15
24
  there, so its component classes would NOT be scanned. This @source is
16
25
  relative to THIS file (packages/velu-ui/src/styles.css); "." == the
17
- velu-ui src tree, so Tailwind also scans velu-ui components. */
26
+ velu-ui src tree, so Tailwind also scans velu-ui components.
27
+
28
+ Project MDX/JSX (the docs author's content) is registered as an extra
29
+ `@source` at `velu dev` / `velu build` time by
30
+ `veluTailwindProjectSource` in velu-cli — so utilities used in content
31
+ (`w-full`, `rounded-xl`, `dark:…`, …) are generated too. */
18
32
  @source ".";
19
33
 
20
- @import './base.css';
21
- @import './primitives/switcher.css';
22
- @import './components/sidebar.css';
23
- @import './components/nav-select.css';
24
- @import './components/context-menu.css';
25
- @import './components/accordion.css';
26
- @import './components/card.css';
27
- @import './components/icon.css';
28
- @import './components/image.css';
29
- @import './components/code-block.css';
30
- @import './components/field.css';
31
- @import './components/prompt.css';
32
- @import './components/steps.css';
33
- @import './components/tree.css';
34
- @import './components/update.css';
35
- @import './components/changelog-filters.css';
36
- @import './components/api.css';
37
- @import './components/api-page.css';
38
- @import './components/not-found.css';
39
- @import './components/ask-bar.css';
40
- @import './components/chatbot.css';
41
- @import './components/page-feedback.css';
42
- @import './components/page-nav.css';
43
- @import './components/page-footer.css';
44
- @import './components/powered-by.css';
45
- @import './components/page-header.css';
46
- @import './components/theme-toggle.css';
47
- @import './components/docs-layout.css';
48
- @import './components/toc-bar.css';
49
- @import './components/search.css';
34
+ @import './base.css' layer(velu);
35
+ @import './element-selectors.css' layer(velu);
36
+ @import './primitives/switcher.css' layer(velu);
37
+ @import './components/sidebar.css' layer(velu);
38
+ @import './components/nav-select.css' layer(velu);
39
+ @import './components/context-menu.css' layer(velu);
40
+ @import './components/accordion.css' layer(velu);
41
+ @import './components/callout.css' layer(velu);
42
+ @import './components/card.css' layer(velu);
43
+ @import './components/icon.css' layer(velu);
44
+ @import './components/image.css' layer(velu);
45
+ @import './components/code-block.css' layer(velu);
46
+ @import './components/field.css' layer(velu);
47
+ @import './components/prompt.css' layer(velu);
48
+ @import './components/steps.css' layer(velu);
49
+ @import './components/tree.css' layer(velu);
50
+ @import './components/update.css' layer(velu);
51
+ @import './components/changelog-filters.css' layer(velu);
52
+ @import './components/api.css' layer(velu);
53
+ @import './components/api-page.css' layer(velu);
54
+ @import './components/not-found.css' layer(velu);
55
+ @import './components/ask-bar.css' layer(velu);
56
+ @import './components/chatbot.css' layer(velu);
57
+ @import './components/page-feedback.css' layer(velu);
58
+ @import './components/page-nav.css' layer(velu);
59
+ @import './components/page-footer.css' layer(velu);
60
+ @import './components/powered-by.css' layer(velu);
61
+ @import './components/page-header.css' layer(velu);
62
+ @import './components/theme-toggle.css' layer(velu);
63
+ @import './components/docs-layout.css' layer(velu);
64
+ @import './components/toc-bar.css' layer(velu);
65
+ @import './components/search.css' layer(velu);
@@ -683,6 +683,19 @@ function ImageDemo() {
683
683
 
684
684
  const RouterLink = ({ href, ...rest }) => <Link to={href} {...rest} />;
685
685
 
686
+ // Look up a heading by its slug id. Prefers a match INSIDE the article: the
687
+ // layout chrome carries stable, Mintlify-compatible ids for custom CSS
688
+ // (#sidebar, #content, #footer, …), and a heading titled "Sidebar" or "Footer"
689
+ // would slug to the same id — document.getElementById would then return the
690
+ // chrome element (earlier in DOM order) and the TOC would scroll to the wrong
691
+ // place.
692
+ function sectionById(id) {
693
+ if (typeof document === 'undefined' || !id) return null;
694
+ const article = document.getElementById('content');
695
+ const esc = id.replace(/["\\]/g, '\\$&');
696
+ return article?.querySelector(`[id="${esc}"]`) ?? document.getElementById(id);
697
+ }
698
+
686
699
  // Flatten a nested TOC tree to {id, label, depth} for scroll-spy.
687
700
  function flat(nodes, depth = 0, out = []) {
688
701
  for (const n of nodes) {
@@ -730,7 +743,7 @@ function useScrollSpy(ids) {
730
743
  let best = ids[0];
731
744
  let bestDist = Infinity;
732
745
  for (const id of ids) {
733
- const node = document.getElementById(id);
746
+ const node = sectionById(id);
734
747
  if (!node) continue;
735
748
  const top = node.getBoundingClientRect().top;
736
749
  if (top >= vh) continue;
@@ -867,6 +880,7 @@ function DocsPage() {
867
880
  options={languageOptions}
868
881
  linkComponent={RouterLink}
869
882
  ariaLabel="Language"
883
+ idPrefix="localization-select"
870
884
  />
871
885
  );
872
886
  const productSwitcher = productOptions.length > 1 && (
@@ -875,6 +889,7 @@ function DocsPage() {
875
889
  options={productOptions}
876
890
  linkComponent={RouterLink}
877
891
  ariaLabel="Product"
892
+ component="nav-dropdown-products-selector"
878
893
  />
879
894
  );
880
895
 
@@ -897,6 +912,9 @@ function DocsPage() {
897
912
  }
898
913
  descTag.setAttribute('content', frontmatter.description || '');
899
914
  document.documentElement.lang = nav?.activeLanguageCode || 'en';
915
+ // Mirrors the SSR'd attribute so custom CSS scoped with
916
+ // html[data-current-path="/x"] follows client-side navigation too.
917
+ document.documentElement.dataset.currentPath = pathname;
900
918
  }, [pathname, isNotFound, frontmatter.title, frontmatter.description, nav?.activeLanguageCode]);
901
919
 
902
920
  // Frontmatter title needs an id so scroll-spy + click-to-scroll work
@@ -1161,6 +1179,7 @@ function DocsPage() {
1161
1179
  label: 'Log out',
1162
1180
  onClick: logoutPrivateAccess,
1163
1181
  kind: 'outlined',
1182
+ component: 'logout-link',
1164
1183
  });
1165
1184
  }
1166
1185
 
@@ -1211,7 +1230,7 @@ function DocsPage() {
1211
1230
 
1212
1231
  const scrollTo = React.useCallback(
1213
1232
  (id) => {
1214
- const node = document.getElementById(id);
1233
+ const node = sectionById(id);
1215
1234
  if (!node) return;
1216
1235
  // 1) Set + lock the active id IMMEDIATELY so the spy doesn't
1217
1236
  // overwrite it during the smooth-scroll animation.
@@ -1242,6 +1261,7 @@ function DocsPage() {
1242
1261
 
1243
1262
  return (
1244
1263
  <div
1264
+ id="body-content"
1245
1265
  className="velu-docs-layout"
1246
1266
  data-chat-open={chatOpen ? 'true' : 'false'}
1247
1267
  data-sidebar-open={sidebarOpen ? 'true' : 'false'}
@@ -1264,6 +1284,7 @@ function DocsPage() {
1264
1284
  {/* Site header — brand, centered search, right-side actions,
1265
1285
  tabs row. Configurable: pass any number of actions / tabs. */}
1266
1286
  <PageHeader
1287
+ id="navbar"
1267
1288
  linkComponent={RouterLink}
1268
1289
  brand={{
1269
1290
  label: site.name,
@@ -1279,6 +1300,7 @@ function DocsPage() {
1279
1300
  center={
1280
1301
  <Cluster space="var(--s-6)" align="center">
1281
1302
  <Search
1303
+ id="search-bar-entry"
1282
1304
  style={{ inlineSize: '30ch' }}
1283
1305
  unavailable={IS_DEV_PREVIEW}
1284
1306
  search={IS_DEV_PREVIEW ? undefined : searchDocs}
@@ -1290,6 +1312,7 @@ function DocsPage() {
1290
1312
  {!IS_DEV_PREVIEW && !askAiHidden && (
1291
1313
  <button
1292
1314
  type="button"
1315
+ id="assistant-entry"
1293
1316
  className="velu-header__action velu-header__action--outlined"
1294
1317
  onClick={() => askAI('')}
1295
1318
  >
@@ -1316,6 +1339,7 @@ function DocsPage() {
1316
1339
  honour `inset-block-start` for fixed positioning the same way
1317
1340
  as plain `top`). */}
1318
1341
  <aside
1342
+ id="sidebar"
1319
1343
  className="velu-docs-layout__aside velu-docs-layout__aside--left"
1320
1344
  style={{
1321
1345
  /* Bottom edge stays a fixed gap above the viewport bottom,
@@ -1400,6 +1424,8 @@ function DocsPage() {
1400
1424
  <li key={it.href} role="none">
1401
1425
  <a
1402
1426
  role="menuitem"
1427
+ data-component="mobile-nav-tabs-item"
1428
+ data-active={it.href === nav?.activeTab ? 'true' : undefined}
1403
1429
  className="velu-docs-layout__drawer-docselect-item"
1404
1430
  href={it.href}
1405
1431
  tabIndex={navOpen ? 0 : -1}
@@ -1429,10 +1455,11 @@ function DocsPage() {
1429
1455
  <span className="velu-show-on-mobile">{languageSwitcher}</span>
1430
1456
  )}
1431
1457
  {anchors.length > 0 && (
1432
- <ul className="velu-docs-anchors">
1458
+ <ul className="velu-docs-anchors" data-component="nav-anchors">
1433
1459
  {anchors.map((a, i) => (
1434
1460
  <li key={i}>
1435
1461
  <a
1462
+ data-component="nav-anchor"
1436
1463
  className="velu-docs-anchors__link"
1437
1464
  href={a.href}
1438
1465
  target="_blank"
@@ -1457,7 +1484,7 @@ function DocsPage() {
1457
1484
  {/* Hairline separating the top anchor links from the nav sections
1458
1485
  (faithful to the sidebar design's anchor↔sidebar divider). */}
1459
1486
  {anchors.length > 0 && (
1460
- <div className="velu-docs-context-divider" aria-hidden="true" />
1487
+ <sidebar-nav-group-divider class="velu-docs-context-divider" aria-hidden="true" />
1461
1488
  )}
1462
1489
  {/* Only this region scrolls — the context zone above stays
1463
1490
  pinned. The up/down arrows overlay its top/bottom edges and
@@ -1466,6 +1493,7 @@ function DocsPage() {
1466
1493
  nav fully to that end. */}
1467
1494
  <div className="velu-docs-nav-region">
1468
1495
  <div
1496
+ id="sidebar-content"
1469
1497
  ref={leftAsideRef}
1470
1498
  className="velu-docs-nav-scroll velu-hide-scrollbar"
1471
1499
  style={{
@@ -1536,6 +1564,7 @@ function DocsPage() {
1536
1564
  Ask-AI chatbot is open (the panel takes that edge). */}
1537
1565
  {!chatOpen && (
1538
1566
  <aside
1567
+ id="table-of-contents"
1539
1568
  ref={rightAsideRef}
1540
1569
  className="velu-docs-layout__aside velu-docs-layout__aside--right velu-hide-scrollbar"
1541
1570
  style={{
@@ -1555,7 +1584,7 @@ function DocsPage() {
1555
1584
  ) : isChangelog && changelogTags.length ? (
1556
1585
  <ChangelogFilters tags={changelogTags} />
1557
1586
  ) : (
1558
- <Toc items={toc} activeId={activeId} onSelect={scrollTo} />
1587
+ <Toc id="table-of-contents-content" items={toc} activeId={activeId} onSelect={scrollTo} />
1559
1588
  )}
1560
1589
  </aside>
1561
1590
  )}
@@ -1590,6 +1619,7 @@ function DocsPage() {
1590
1619
  on tab/mobile we keep the normal TOC bar (no filters). */}
1591
1620
  <TocBar items={toc} activeId={activeId} onSelect={scrollTo} />
1592
1621
  <main
1622
+ id="content-area"
1593
1623
  className="velu-docs-layout__main"
1594
1624
  style={{
1595
1625
  /* No padding-block-end — the article's last child (PoweredBy)
@@ -1624,7 +1654,7 @@ function DocsPage() {
1624
1654
  })}
1625
1655
  </span>
1626
1656
  </button>
1627
- <div className="velu-docs-layout__article" data-pagefind-body="">
1657
+ <div id="content" className="velu-docs-layout__article" data-pagefind-body="">
1628
1658
  {/* Per-page agent/IDE action bar: the section eyebrow + a
1629
1659
  "Copy Page" split-button whose dropdown is driven by the
1630
1660
  Mintlify-compatible `contextual` config. Renders nothing
@@ -1672,7 +1702,7 @@ function DocsPage() {
1672
1702
  level. Falls back to a not-found / missing-file notice
1673
1703
  when the route has no page (or its file is absent). */}
1674
1704
  <MDXProvider components={defaultMdxComponents}>
1675
- <div className="velu-prose">
1705
+ <mdx-content class="velu-prose">
1676
1706
  {PageComponent ? (
1677
1707
  <ErrorBoundary key={pathname} file={entry?.relPath}>
1678
1708
  <PageComponent />
@@ -1684,7 +1714,7 @@ function DocsPage() {
1684
1714
  : 'Page not found.'}
1685
1715
  </p>
1686
1716
  )}
1687
- </div>
1717
+ </mdx-content>
1688
1718
  </MDXProvider>
1689
1719
  {/* Page-foot feedback widget — ref'd so the sticky AskBar
1690
1720
  above can hide as the user scrolls near it. */}
@@ -1706,6 +1736,7 @@ function DocsPage() {
1706
1736
  sidebar reading order of the active section. */}
1707
1737
  {(nav?.prev || nav?.next) && (
1708
1738
  <PageNav
1739
+ id="pagination"
1709
1740
  style={{ marginTop: 'var(--s2)' }}
1710
1741
  prev={nav?.prev}
1711
1742
  next={nav?.next}
@@ -1764,7 +1795,8 @@ function DocsPage() {
1764
1795
  of the fixed sidebar and TOC as the page scrolls into it, and the ref
1765
1796
  is watched so the asides can pad their bottom by the overlap. */}
1766
1797
  {footerHasLinks && (
1767
- <div
1798
+ <advanced-footer
1799
+ id="footer"
1768
1800
  ref={footerRef}
1769
1801
  data-velu-footer
1770
1802
  style={{ position: 'relative', zIndex: 20 }}
@@ -1774,7 +1806,7 @@ function DocsPage() {
1774
1806
  columns={site.footer.links}
1775
1807
  socials={footerSocials}
1776
1808
  />
1777
- </div>
1809
+ </advanced-footer>
1778
1810
  )}
1779
1811
 
1780
1812
  {/* Ask-AI chatbot — slides in from the inline-end edge. Hidden when the
@@ -1817,7 +1849,7 @@ function DocsPage() {
1817
1849
  let tries = 0;
1818
1850
  const go = () => {
1819
1851
  if (hash) {
1820
- if (document.getElementById(hash)) { scrollTo(hash); return; }
1852
+ if (sectionById(hash)) { scrollTo(hash); return; }
1821
1853
  if (tries++ < 10) { setTimeout(go, 60); return; }
1822
1854
  }
1823
1855
  window.scrollTo({ top: 0, behavior: 'smooth' });
@@ -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
+ }
@@ -1,3 +1,4 @@
1
+ import './dev-warnings.js';
1
2
  import React from 'react';
2
3
  import { renderToString } from 'react-dom/server';
3
4
  import { StaticRouter } from 'react-router-dom/server';