@veluai/velu 0.2.36 → 0.2.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/cli.js +122 -55
- package/docs/mintlify-migration.md +159 -0
- package/docs/vepa.md +97 -0
- package/package.json +8 -2
- package/runtime/velu-ui/components/ApiReferencePage.jsx +11 -3
- package/runtime/velu-ui/components/ApiSamples.jsx +9 -4
- package/runtime/velu-ui/components/Card.jsx +7 -4
- package/runtime/velu-ui/components/Chatbot.jsx +9 -4
- package/runtime/velu-ui/components/Columns.jsx +1 -0
- package/runtime/velu-ui/components/PageFooter.jsx +3 -0
- package/runtime/velu-ui/components/PageHeader.jsx +14 -7
- package/runtime/velu-ui/components/ThemePreferenceMenu.jsx +57 -0
- package/runtime/velu-ui/components/ThemeToggle.jsx +3 -1
- package/runtime/velu-ui/components/Update.jsx +22 -10
- package/runtime/velu-ui/components/VepaFooter.jsx +40 -0
- package/runtime/velu-ui/components/card.css +9 -0
- package/runtime/velu-ui/components/chatbot.css +42 -0
- package/runtime/velu-ui/components/docs-layout.css +75 -0
- package/runtime/velu-ui/components/page-header.css +6 -0
- package/runtime/velu-ui/styles.css +1 -0
- package/runtime/velu-ui/themes/vepa.css +396 -0
- package/schema/velu.schema.json +5 -0
- package/src/navigation.js +3 -2
- package/src/runtime/App.jsx +120 -50
- package/src/runtime/VepaToc.jsx +12 -0
- package/src/runtime/page-mode.js +54 -0
- package/templates/starter/essentials/settings.mdx +15 -0
|
@@ -10,9 +10,11 @@ import resolveIcon from '../lib/resolveIcon.jsx';
|
|
|
10
10
|
* description and tags) and a right column with the entry body (the MDX
|
|
11
11
|
* children — usually `##` sub-headings and prose).
|
|
12
12
|
*
|
|
13
|
-
* The section gets a stable `id` derived from
|
|
14
|
-
* and shows up in the table of contents.
|
|
15
|
-
* `
|
|
13
|
+
* The section gets a stable `id` derived from description (preferred) or
|
|
14
|
+
* label so each entry is linkable and shows up in the table of contents.
|
|
15
|
+
* Preferring `description` keeps same-month releases unique (e.g. two
|
|
16
|
+
* "September 2026" rows with 0.2.37 / 0.2.36). The id MUST match the slug
|
|
17
|
+
* the `extract-toc` remark plugin computes (see
|
|
16
18
|
* velu-cli/src/mdx-plugins/extract-toc.js → `slugifyUpdate`), so click→scroll
|
|
17
19
|
* and scroll-spy line up.
|
|
18
20
|
*
|
|
@@ -25,12 +27,20 @@ import resolveIcon from '../lib/resolveIcon.jsx';
|
|
|
25
27
|
* tags?: string | string[], rss?: any, children?: React.ReactNode,
|
|
26
28
|
* className?: string }} props
|
|
27
29
|
*/
|
|
28
|
-
export function slugifyUpdate(
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
export function slugifyUpdate(label, description) {
|
|
31
|
+
const slugPart = (value) =>
|
|
32
|
+
String(value || '')
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
35
|
+
.replace(/^-+|-+$/g, '');
|
|
36
|
+
const labelPart = slugPart(label);
|
|
37
|
+
const descPart = description == null || description === '' ? '' : slugPart(description);
|
|
38
|
+
const base = descPart
|
|
39
|
+
? labelPart
|
|
40
|
+
? `${labelPart}-${descPart}`
|
|
41
|
+
: descPart
|
|
42
|
+
: labelPart || 'item';
|
|
43
|
+
return `update-${base}`;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
function toTagList(value) {
|
|
@@ -51,7 +61,9 @@ export default function Update({
|
|
|
51
61
|
}) {
|
|
52
62
|
void rss;
|
|
53
63
|
const updateLabel = String(label ?? date ?? 'Update');
|
|
54
|
-
const
|
|
64
|
+
const updateDescription =
|
|
65
|
+
description == null || description === '' ? undefined : String(description);
|
|
66
|
+
const anchorId = slugifyUpdate(updateLabel, updateDescription);
|
|
55
67
|
const tagList = toTagList(tags);
|
|
56
68
|
|
|
57
69
|
return (
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Logo from './Logo.jsx';
|
|
3
|
+
import SocialLinks from './SocialLinks.jsx';
|
|
4
|
+
import ThemePreferenceMenu from './ThemePreferenceMenu.jsx';
|
|
5
|
+
|
|
6
|
+
export default function VepaFooter({ brand, columns = [], socials = [], linkComponent: Link = 'a' }) {
|
|
7
|
+
const single = columns.length === 1;
|
|
8
|
+
const completeHeadings = columns.every((c) => c.title);
|
|
9
|
+
return <footer id="footer" className="vepa-footer" data-layout={columns.length <= 3 ? 'compact' : 'expanded'} data-single={single} data-headings={completeHeadings}>
|
|
10
|
+
<div className="vepa-footer__inner">
|
|
11
|
+
<div className="vepa-footer__top">
|
|
12
|
+
<div className="vepa-footer__brand-column">
|
|
13
|
+
<Link href={brand?.href || '/'} className="vepa-footer__brand">
|
|
14
|
+
{brand?.logo ? <Logo logo={brand.logo} alt={brand.label} /> : <span>{brand?.label || 'Velu'}</span>}
|
|
15
|
+
</Link>
|
|
16
|
+
<SocialLinks socials={socials} className="vepa-footer__brand-socials" />
|
|
17
|
+
</div>
|
|
18
|
+
<div className="vepa-footer__columns" style={{ '--footer-columns': columns.length }}>
|
|
19
|
+
{columns.map((column, i) => <div className="vepa-footer__column" key={i}>
|
|
20
|
+
<div className="vepa-footer__links">
|
|
21
|
+
{column.title && !single && <p className="vepa-footer__heading">{column.title}</p>}
|
|
22
|
+
{(column.items || []).map((item, j) => {
|
|
23
|
+
const external = /^https?:\/\//.test(item.href || '');
|
|
24
|
+
const Tag = external ? 'a' : Link;
|
|
25
|
+
return <Tag className="vepa-footer__link" key={j} href={item.href || '#'} {...(external ? { target: '_blank', rel: 'noreferrer' } : {})}>{item.label}</Tag>;
|
|
26
|
+
})}
|
|
27
|
+
</div>
|
|
28
|
+
</div>)}
|
|
29
|
+
</div>
|
|
30
|
+
<SocialLinks socials={socials} className="vepa-footer__end-socials" />
|
|
31
|
+
</div>
|
|
32
|
+
{!completeHeadings && <SocialLinks socials={socials} className="vepa-footer__mobile-socials" />}
|
|
33
|
+
<div className="vepa-footer__divider" />
|
|
34
|
+
<div className="vepa-footer__bottom">
|
|
35
|
+
<a href="https://veludocs.com" target="_blank" rel="noreferrer" className="vepa-footer__powered">Powered by <strong>Velu</strong></a>
|
|
36
|
+
<ThemePreferenceMenu inline />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</footer>;
|
|
40
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
(cta hover, image clip, group grid) so this has CSS. Namespaced. */
|
|
3
3
|
|
|
4
4
|
.velu-card {
|
|
5
|
+
position: relative;
|
|
5
6
|
display: flex;
|
|
6
7
|
flex-direction: column;
|
|
7
8
|
border: var(--border-width) solid var(--border-color);
|
|
@@ -11,6 +12,14 @@
|
|
|
11
12
|
color: var(--text-color);
|
|
12
13
|
text-decoration: none;
|
|
13
14
|
}
|
|
15
|
+
.velu-card__link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
|
|
16
|
+
.velu-card:has(> .velu-card__link):hover { border-color: var(--accent-color); }
|
|
17
|
+
.velu-card__link:focus-visible { outline: 2px solid var(--accent-color); outline-offset: -2px; }
|
|
18
|
+
/* Nested controls must sit above the stretched-link overlay, or clicks hit the card href. */
|
|
19
|
+
.velu-card :is(a, button, input, select, textarea, summary, label):not(.velu-card__link) {
|
|
20
|
+
position: relative;
|
|
21
|
+
z-index: 2;
|
|
22
|
+
}
|
|
14
23
|
|
|
15
24
|
/* CTA card: the whole card is the click target (stretched-link pattern —
|
|
16
25
|
the CTA stays the single real <a>; a ::after overlay makes the entire
|
|
@@ -45,6 +45,31 @@
|
|
|
45
45
|
0 0 var(--s2) color-mix(in srgb, #000 18%, transparent),
|
|
46
46
|
0 0 var(--s0) color-mix(in srgb, #000 8%, transparent);
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/* Full-page Ask AI (`mode: assistant`). Sits under the navbar and
|
|
50
|
+
replaces the docs body instead of sliding in as a side panel. */
|
|
51
|
+
.velu-chatbot--page {
|
|
52
|
+
inset-block-start: var(--velu-header-height, 3.5rem);
|
|
53
|
+
inset-block-end: 0;
|
|
54
|
+
inset-inline: 0;
|
|
55
|
+
inline-size: 100%;
|
|
56
|
+
max-inline-size: none;
|
|
57
|
+
transform: none;
|
|
58
|
+
border-inline-start: 0;
|
|
59
|
+
border-block-start: var(--border-width) solid var(--surface-color);
|
|
60
|
+
box-shadow: none;
|
|
61
|
+
z-index: 5;
|
|
62
|
+
/* Side gutters so the full-page chat isn't flush to the viewport. */
|
|
63
|
+
padding-inline: var(--s4);
|
|
64
|
+
}
|
|
65
|
+
.velu-chatbot--page .velu-chatbot__header,
|
|
66
|
+
.velu-chatbot--page .velu-chatbot__body,
|
|
67
|
+
.velu-chatbot--page .velu-chatbot__composer,
|
|
68
|
+
.velu-chatbot--page .velu-chatbot__history {
|
|
69
|
+
max-inline-size: 42rem;
|
|
70
|
+
inline-size: 100%;
|
|
71
|
+
margin-inline: auto;
|
|
72
|
+
}
|
|
48
73
|
/* base.css applies a global `* { max-width: 66ch }`; reset it so the
|
|
49
74
|
panel's structural elements size to flex/the panel, not 66ch. */
|
|
50
75
|
.velu-chatbot * {
|
|
@@ -118,6 +143,23 @@
|
|
|
118
143
|
.velu-chatbot--open {
|
|
119
144
|
transform: translateY(0);
|
|
120
145
|
}
|
|
146
|
+
/* Page-mode assistant stays a full-viewport chat, not a bottom sheet. */
|
|
147
|
+
.velu-chatbot--page,
|
|
148
|
+
.velu-chatbot--page.velu-chatbot--open {
|
|
149
|
+
inset-block-start: var(--velu-header-height, 3.5rem);
|
|
150
|
+
inset-block-end: 0;
|
|
151
|
+
inset-inline: 0;
|
|
152
|
+
inline-size: 100%;
|
|
153
|
+
max-inline-size: none;
|
|
154
|
+
block-size: auto;
|
|
155
|
+
transform: none;
|
|
156
|
+
border-radius: 0;
|
|
157
|
+
padding-block-start: 0;
|
|
158
|
+
box-shadow: none;
|
|
159
|
+
}
|
|
160
|
+
.velu-chatbot--page .velu-chatbot__drag-handle {
|
|
161
|
+
display: none;
|
|
162
|
+
}
|
|
121
163
|
}
|
|
122
164
|
|
|
123
165
|
/* ── Header ─────────────────────────────────────────────────────────── */
|
|
@@ -808,6 +808,81 @@
|
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
|
|
811
|
+
/* ── Page modes (frontmatter `mode`) ─────────────────────────────────── */
|
|
812
|
+
/* Mintlify-compatible layouts. Default is the current chrome (sidebar +
|
|
813
|
+
TOC + footer) and needs no extra rules. */
|
|
814
|
+
|
|
815
|
+
/* Wide: drop the right rail, keep the right gutter, let the article
|
|
816
|
+
use the extra width inside that gutter. */
|
|
817
|
+
.velu-docs-layout[data-page-mode='wide'] .velu-docs-layout__aside--right,
|
|
818
|
+
.velu-docs-layout[data-page-mode='frame'] .velu-docs-layout__aside--right {
|
|
819
|
+
display: none;
|
|
820
|
+
}
|
|
821
|
+
.velu-docs-layout[data-page-mode='wide'] .velu-docs-layout__article {
|
|
822
|
+
max-inline-size: none;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* Frame is custom canvas + sidebar: no TOC gutter; the landing column
|
|
826
|
+
is centered in the remaining width next to the sidebar. */
|
|
827
|
+
.velu-docs-layout[data-page-mode='frame'] .velu-docs-layout__center {
|
|
828
|
+
margin-inline-end: 0;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/* Custom / center / assistant: no sidebar, no TOC. */
|
|
832
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__aside,
|
|
833
|
+
.velu-docs-layout[data-page-mode='center'] .velu-docs-layout__aside,
|
|
834
|
+
.velu-docs-layout[data-page-mode='assistant'] .velu-docs-layout__aside {
|
|
835
|
+
display: none;
|
|
836
|
+
}
|
|
837
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__center,
|
|
838
|
+
.velu-docs-layout[data-page-mode='center'] .velu-docs-layout__center,
|
|
839
|
+
.velu-docs-layout[data-page-mode='assistant'] .velu-docs-layout__center {
|
|
840
|
+
margin-inline: 0;
|
|
841
|
+
}
|
|
842
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__sidebar-toggle,
|
|
843
|
+
.velu-docs-layout[data-page-mode='center'] .velu-docs-layout__sidebar-toggle,
|
|
844
|
+
.velu-docs-layout[data-page-mode='assistant'] .velu-docs-layout__sidebar-toggle,
|
|
845
|
+
.velu-docs-layout[data-page-mode='wide'] .velu-toc-bar,
|
|
846
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-toc-bar,
|
|
847
|
+
.velu-docs-layout[data-page-mode='frame'] .velu-toc-bar,
|
|
848
|
+
.velu-docs-layout[data-page-mode='center'] .velu-toc-bar,
|
|
849
|
+
.velu-docs-layout[data-page-mode='assistant'] .velu-toc-bar {
|
|
850
|
+
display: none;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/* Custom / frame are a landing canvas: center a max-w-4xl column.
|
|
854
|
+
Frame keeps the sidebar; custom does not. */
|
|
855
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__main,
|
|
856
|
+
.velu-docs-layout[data-page-mode='frame'] .velu-docs-layout__main {
|
|
857
|
+
padding-block: calc(var(--s5) * 2);
|
|
858
|
+
padding-inline: var(--s4);
|
|
859
|
+
}
|
|
860
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__article,
|
|
861
|
+
.velu-docs-layout[data-page-mode='frame'] .velu-docs-layout__article {
|
|
862
|
+
max-inline-size: 56rem;
|
|
863
|
+
margin-inline: auto;
|
|
864
|
+
}
|
|
865
|
+
.velu-docs-layout[data-page-mode='custom'] .velu-prose > :is(h1, h2, h3, p),
|
|
866
|
+
.velu-docs-layout[data-page-mode='frame'] .velu-prose > :is(h1, h2, h3, p) {
|
|
867
|
+
max-width: none;
|
|
868
|
+
text-align: center;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/* Assistant page: the chat fills the viewport below the header. Hide
|
|
872
|
+
the empty centre column when the chat is showing (no disabled-search
|
|
873
|
+
fallback). */
|
|
874
|
+
.velu-docs-layout[data-page-mode='assistant']
|
|
875
|
+
.velu-docs-layout__center:not(:has(.velu-assistant-disabled)) {
|
|
876
|
+
display: none;
|
|
877
|
+
}
|
|
878
|
+
.velu-assistant-disabled {
|
|
879
|
+
display: flex;
|
|
880
|
+
align-items: center;
|
|
881
|
+
justify-content: center;
|
|
882
|
+
min-block-size: 70vh;
|
|
883
|
+
padding: var(--s4) var(--s2);
|
|
884
|
+
}
|
|
885
|
+
|
|
811
886
|
/* ── 404 takeover ─────────────────────────────────────────────────────── */
|
|
812
887
|
/* On a not-found route the docs chrome (both fixed asides) is hidden and the
|
|
813
888
|
centre column spans full width to center the <NotFound> content. The header
|
|
@@ -39,6 +39,12 @@
|
|
|
39
39
|
color-mix(in srgb, #000 55%, transparent);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
.velu-header--no-tabs {
|
|
43
|
+
/* Tabs row is gone — restore bottom padding so brand / search /
|
|
44
|
+
actions don't sit on the header hairline. Matches the top inset. */
|
|
45
|
+
padding-block-end: var(--s-1);
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
/* Once the user has scrolled even a single pixel, the header becomes
|
|
43
49
|
a translucent frosted-glass overlay so the article content shows
|
|
44
50
|
through faintly underneath. color-mix keeps the recipe token-driven:
|