@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
|
@@ -63,9 +63,9 @@ function hasActiveDescendant(item, activeHref) {
|
|
|
63
63
|
function Icon({ icon }) {
|
|
64
64
|
const node = resolveIcon(icon);
|
|
65
65
|
return node ? (
|
|
66
|
-
<
|
|
66
|
+
<sidebar-group-icon class="velu-sidebar__icon" aria-hidden="true">
|
|
67
67
|
{node}
|
|
68
|
-
</
|
|
68
|
+
</sidebar-group-icon>
|
|
69
69
|
) : null;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -99,7 +99,7 @@ function Node({ item, depth }) {
|
|
|
99
99
|
return (
|
|
100
100
|
<li>
|
|
101
101
|
<details open={hasActiveDescendant(item, activeHref) || undefined}>
|
|
102
|
-
<summary className={`${base} velu-sidebar__summary`}>
|
|
102
|
+
<summary className={`${base} velu-sidebar__summary`} data-component="sidebar-group-header">
|
|
103
103
|
<Icon icon={icon} />
|
|
104
104
|
<span className="velu-sidebar__label">{label}</span>
|
|
105
105
|
<Chevron />
|
|
@@ -124,10 +124,11 @@ function Node({ item, depth }) {
|
|
|
124
124
|
: { href };
|
|
125
125
|
|
|
126
126
|
return (
|
|
127
|
-
<li>
|
|
127
|
+
<li data-active={active ? 'true' : undefined}>
|
|
128
128
|
<LinkTag
|
|
129
129
|
className={cls}
|
|
130
130
|
aria-current={active ? 'page' : undefined}
|
|
131
|
+
data-active={active ? 'true' : undefined}
|
|
131
132
|
{...linkProps}
|
|
132
133
|
>
|
|
133
134
|
<Icon icon={icon} />
|
|
@@ -228,8 +229,19 @@ export default function Sidebar({
|
|
|
228
229
|
separates one section from the next — compact but still
|
|
229
230
|
visibly grouped. */}
|
|
230
231
|
{sections.map((section, i) => (
|
|
231
|
-
<Stack
|
|
232
|
-
|
|
232
|
+
<Stack
|
|
233
|
+
key={i}
|
|
234
|
+
as="sidebar-group"
|
|
235
|
+
space="var(--s-4)"
|
|
236
|
+
data-active={
|
|
237
|
+
section.items.some(
|
|
238
|
+
(it) => isActive(it, activeHref) || hasActiveDescendant(it, activeHref),
|
|
239
|
+
)
|
|
240
|
+
? 'true'
|
|
241
|
+
: undefined
|
|
242
|
+
}
|
|
243
|
+
>
|
|
244
|
+
<h5 className="velu-sidebar__section" data-component="sidebar-title">
|
|
233
245
|
<Icon icon={section.icon} />
|
|
234
246
|
{section.title}
|
|
235
247
|
</h5>
|
|
@@ -33,7 +33,7 @@ export function Step({
|
|
|
33
33
|
...rest
|
|
34
34
|
}) {
|
|
35
35
|
return (
|
|
36
|
-
<
|
|
36
|
+
<step className={`velu-step ${className}`.trim()} {...rest}>
|
|
37
37
|
<div className="velu-step__circle" aria-hidden="true">
|
|
38
38
|
{index}
|
|
39
39
|
</div>
|
|
@@ -44,7 +44,7 @@ export function Step({
|
|
|
44
44
|
{children != null && children !== false && (
|
|
45
45
|
<div className="velu-step__body">{children}</div>
|
|
46
46
|
)}
|
|
47
|
-
</
|
|
47
|
+
</step>
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
Step.displayName = 'Step';
|
|
@@ -58,8 +58,8 @@ export default function Steps({ children, className = '', ...rest }) {
|
|
|
58
58
|
return cloneElement(child, { index: child.props.index ?? i });
|
|
59
59
|
});
|
|
60
60
|
return (
|
|
61
|
-
<
|
|
61
|
+
<steps className={`velu-steps ${className}`.trim()} {...rest}>
|
|
62
62
|
{numbered}
|
|
63
|
-
</
|
|
63
|
+
</steps>
|
|
64
64
|
);
|
|
65
65
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Sun, Moon, Monitor, Check } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export default function ThemePreferenceMenu({ inline = false }) {
|
|
5
|
+
const [open, setOpen] = React.useState(false);
|
|
6
|
+
const [preference, setPreference] = React.useState('system');
|
|
7
|
+
const root = React.useRef(null);
|
|
8
|
+
const trigger = React.useRef(null);
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
11
|
+
const sync = () => {
|
|
12
|
+
let pref = 'system';
|
|
13
|
+
try { pref = localStorage.getItem('velu-theme') || 'system'; } catch {}
|
|
14
|
+
setPreference(pref);
|
|
15
|
+
document.documentElement.dataset.theme = pref === 'system' ? (media.matches ? 'dark' : 'light') : pref;
|
|
16
|
+
};
|
|
17
|
+
sync();
|
|
18
|
+
media.addEventListener('change', sync);
|
|
19
|
+
window.addEventListener('storage', sync);
|
|
20
|
+
window.addEventListener('velu-theme-change', sync);
|
|
21
|
+
return () => { media.removeEventListener('change', sync); window.removeEventListener('storage', sync); window.removeEventListener('velu-theme-change', sync); };
|
|
22
|
+
}, []);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (!open) return;
|
|
25
|
+
root.current?.querySelector('[aria-checked="true"]')?.focus();
|
|
26
|
+
const outside = (e) => { if (!root.current?.contains(e.target)) setOpen(false); };
|
|
27
|
+
document.addEventListener('pointerdown', outside);
|
|
28
|
+
return () => document.removeEventListener('pointerdown', outside);
|
|
29
|
+
}, [open]);
|
|
30
|
+
const choose = (value) => {
|
|
31
|
+
setPreference(value);
|
|
32
|
+
try { value === 'system' ? localStorage.removeItem('velu-theme') : localStorage.setItem('velu-theme', value); } catch {}
|
|
33
|
+
document.documentElement.dataset.theme = value === 'system' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : value;
|
|
34
|
+
window.dispatchEvent(new Event('velu-theme-change'));
|
|
35
|
+
setOpen(false);
|
|
36
|
+
trigger.current?.focus();
|
|
37
|
+
};
|
|
38
|
+
const Icon = preference === 'system' ? Monitor : preference === 'dark' ? Moon : Sun;
|
|
39
|
+
if (inline) return <div className="vepa-footer-theme" role="group" aria-label="Color theme">
|
|
40
|
+
{[[Monitor, 'system', 'System'], [Sun, 'light', 'Light'], [Moon, 'dark', 'Dark']].map(([Glyph, value, label]) => <button key={value} type="button" aria-label={`${label} theme`} aria-pressed={preference === value} onClick={() => choose(value)}><Glyph size={16} aria-hidden="true" /></button>)}
|
|
41
|
+
</div>;
|
|
42
|
+
return <div ref={root} className="velu-theme-menu" onKeyDown={(e) => {
|
|
43
|
+
if (e.key === 'Escape') { setOpen(false); trigger.current?.focus(); }
|
|
44
|
+
if (e.key === 'Tab') setOpen(false);
|
|
45
|
+
if (open && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const items = [...root.current.querySelectorAll('[role="menuitemradio"]')];
|
|
48
|
+
const i = items.indexOf(document.activeElement);
|
|
49
|
+
items[e.key === 'Home' ? 0 : e.key === 'End' ? 2 : (i + (e.key === 'ArrowUp' ? 2 : 1)) % 3]?.focus();
|
|
50
|
+
}
|
|
51
|
+
}}>
|
|
52
|
+
<button ref={trigger} type="button" className="velu-theme-menu__trigger" aria-label="Theme preference" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen(!open)}><Icon size={16} aria-hidden="true" /></button>
|
|
53
|
+
{open && <div className="velu-theme-menu__options" role="menu" aria-label="Theme preference">
|
|
54
|
+
{[[Sun, 'light', 'Light'], [Moon, 'dark', 'Dark'], [Monitor, 'system', 'System']].map(([Glyph, value, label]) => <button key={value} type="button" role="menuitemradio" aria-checked={preference === value} tabIndex={-1} onClick={() => choose(value)}><Glyph size={16} aria-hidden="true" /><span>{label}</span>{preference === value && <Check size={14} aria-hidden="true" />}</button>)}
|
|
55
|
+
</div>}
|
|
56
|
+
</div>;
|
|
57
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Sun, Moon } from 'lucide-react';
|
|
3
|
+
import ThemePreferenceMenu from './ThemePreferenceMenu.jsx';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* ThemeToggle — pill-shaped two-state light/dark switch. The thumb
|
|
@@ -14,7 +15,8 @@ import { Sun, Moon } from 'lucide-react';
|
|
|
14
15
|
* explicit choice (which then overrides the OS preference on future
|
|
15
16
|
* visits, per the anti-flash script in the template).
|
|
16
17
|
*/
|
|
17
|
-
export default function ThemeToggle({ className = '', ...rest }) {
|
|
18
|
+
export default function ThemeToggle({ className = '', variant, ...rest }) {
|
|
19
|
+
if (variant === 'menu') return <ThemePreferenceMenu />;
|
|
18
20
|
function toggle() {
|
|
19
21
|
const root = document.documentElement;
|
|
20
22
|
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
@@ -32,6 +34,7 @@ export default function ThemeToggle({ className = '', ...rest }) {
|
|
|
32
34
|
onClick={toggle}
|
|
33
35
|
aria-label="Toggle color theme"
|
|
34
36
|
title="Toggle color theme"
|
|
37
|
+
data-component-name="theme-toggle"
|
|
35
38
|
className={`velu-theme-toggle ${className}`.trim()}
|
|
36
39
|
{...rest}
|
|
37
40
|
>
|
|
@@ -127,9 +127,11 @@ const Row = React.forwardRef(function Row(
|
|
|
127
127
|
ref
|
|
128
128
|
) {
|
|
129
129
|
return (
|
|
130
|
-
<
|
|
130
|
+
<toc-item
|
|
131
131
|
ref={ref}
|
|
132
132
|
data-toc-active={active ? 'true' : undefined}
|
|
133
|
+
data-active={active ? 'true' : undefined}
|
|
134
|
+
data-active-deepest={active ? 'true' : undefined}
|
|
133
135
|
onMouseEnter={() => onEnter(idx)}
|
|
134
136
|
onClick={() => onClick(item.id)}
|
|
135
137
|
style={{
|
|
@@ -166,7 +168,7 @@ const Row = React.forwardRef(function Row(
|
|
|
166
168
|
>
|
|
167
169
|
{item.label}
|
|
168
170
|
</span>
|
|
169
|
-
</
|
|
171
|
+
</toc-item>
|
|
170
172
|
);
|
|
171
173
|
});
|
|
172
174
|
|
|
@@ -434,7 +436,7 @@ export default function Toc({
|
|
|
434
436
|
}, [activeIdx]);
|
|
435
437
|
|
|
436
438
|
return (
|
|
437
|
-
<
|
|
439
|
+
<toc
|
|
438
440
|
ref={rootRef}
|
|
439
441
|
onMouseLeave={() => setHoverIdx(null)}
|
|
440
442
|
style={{
|
|
@@ -532,6 +534,6 @@ export default function Toc({
|
|
|
532
534
|
fontSize={fontSize}
|
|
533
535
|
/>
|
|
534
536
|
))}
|
|
535
|
-
</
|
|
537
|
+
</toc>
|
|
536
538
|
);
|
|
537
539
|
}
|
|
@@ -25,9 +25,9 @@ import resolveIcon from '../lib/resolveIcon.jsx';
|
|
|
25
25
|
|
|
26
26
|
export default function Tree({ children, className = '', ...rest }) {
|
|
27
27
|
return (
|
|
28
|
-
<
|
|
28
|
+
<tree className={`velu-tree ${className}`.trim()} {...rest}>
|
|
29
29
|
{children}
|
|
30
|
-
</
|
|
30
|
+
</tree>
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -54,6 +54,7 @@ export function Folder({
|
|
|
54
54
|
// the <details> is not [open], so the rail collapses with them.
|
|
55
55
|
return (
|
|
56
56
|
<details
|
|
57
|
+
data-component="tree-folder"
|
|
57
58
|
className={`velu-tree__group ${className}`.trim()}
|
|
58
59
|
open={defaultOpen || undefined}
|
|
59
60
|
{...rest}
|
|
@@ -76,12 +77,12 @@ export function File({
|
|
|
76
77
|
...rest
|
|
77
78
|
}) {
|
|
78
79
|
return (
|
|
79
|
-
<
|
|
80
|
-
|
|
80
|
+
<tree-file
|
|
81
|
+
class={`velu-tree__item velu-tree__item--file ${className}`.trim()}
|
|
81
82
|
{...rest}
|
|
82
83
|
>
|
|
83
84
|
<ItemIcon icon={icon} />
|
|
84
85
|
<span className="velu-tree__label">{name}</span>
|
|
85
|
-
</
|
|
86
|
+
</tree-file>
|
|
86
87
|
);
|
|
87
88
|
}
|
|
@@ -55,7 +55,7 @@ export default function Update({
|
|
|
55
55
|
const tagList = toTagList(tags);
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
|
-
<
|
|
58
|
+
<update
|
|
59
59
|
id={anchorId}
|
|
60
60
|
data-update-label={updateLabel}
|
|
61
61
|
data-update-tags={tagList.join('|')}
|
|
@@ -87,6 +87,6 @@ export default function Update({
|
|
|
87
87
|
) : null}
|
|
88
88
|
</div>
|
|
89
89
|
{children ? <div className="velu-update__content">{children}</div> : null}
|
|
90
|
-
</
|
|
90
|
+
</update>
|
|
91
91
|
);
|
|
92
92
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* Callout — admonition box. Static styles live here (not inline) so a
|
|
2
|
+
project's custom CSS can override them with a plain `callout { ... }`
|
|
3
|
+
element selector, as on Mintlify. The per-variant colours arrive as inline
|
|
4
|
+
custom properties (--velu-callout-bg / --velu-callout-stroke) set by
|
|
5
|
+
Callout.jsx from the type / stroke / bg props. */
|
|
6
|
+
.velu-callout {
|
|
7
|
+
display: flow-root; /* contains the floated icon, no clipping */
|
|
8
|
+
padding: var(--s0) var(--s3);
|
|
9
|
+
background: var(--velu-callout-bg);
|
|
10
|
+
border: var(--border-width) solid var(--velu-callout-stroke);
|
|
11
|
+
border-radius: var(--radius-sm);
|
|
12
|
+
color: var(--text-color);
|
|
13
|
+
font-size: var(--f-body);
|
|
14
|
+
line-height: var(--lh-body);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The icon is FLOATED into the text flow (not a fixed column): it sits on the
|
|
18
|
+
first line and the text wraps beside it, then flows back under it. One
|
|
19
|
+
line-box tall so it's centred on the first line. */
|
|
20
|
+
.velu-callout__icon {
|
|
21
|
+
float: left;
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
block-size: calc(var(--f-body) * var(--lh-body));
|
|
25
|
+
margin-inline-end: var(--s-1);
|
|
26
|
+
color: var(--velu-callout-stroke);
|
|
27
|
+
font-size: var(--f-body);
|
|
28
|
+
}
|
|
@@ -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 ─────────────────────────────────────────────────────────── */
|
|
@@ -71,8 +71,13 @@
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
.velu-docs-layout__aside--left {
|
|
74
|
+
/* Shared surface token for the rail and its sticky descendants. Project
|
|
75
|
+
CSS can override this once on #sidebar; section headers and their fade
|
|
76
|
+
then remain visually continuous with the customized rail. */
|
|
77
|
+
--velu-sidebar-bg: var(--page-bg);
|
|
74
78
|
inset-inline-start: 0;
|
|
75
79
|
inline-size: var(--velu-sidebar-width);
|
|
80
|
+
background: var(--velu-sidebar-bg);
|
|
76
81
|
/* Left gutter so the nav sits inset from the viewport edge rather
|
|
77
82
|
than hugging it. (At mobile the @container block resets this to 0
|
|
78
83
|
— the drawer manages its own padding.) */
|
|
@@ -803,6 +808,81 @@
|
|
|
803
808
|
}
|
|
804
809
|
}
|
|
805
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
|
+
|
|
806
886
|
/* ── 404 takeover ─────────────────────────────────────────────────────── */
|
|
807
887
|
/* On a not-found route the docs chrome (both fixed asides) is hidden and the
|
|
808
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:
|
|
@@ -58,15 +58,15 @@
|
|
|
58
58
|
takes over (iOS-list style). It sticks within its section wrapper
|
|
59
59
|
(Sidebar.jsx renders each section in its own Stack), which bounds
|
|
60
60
|
the push-out. Full-width opaque background so scrolling items
|
|
61
|
-
don't show behind it; --
|
|
62
|
-
|
|
61
|
+
don't show behind it; --velu-sidebar-bg is owned by the left rail and can
|
|
62
|
+
be customized there, so the bar remains continuous with its surroundings. */
|
|
63
63
|
position: sticky;
|
|
64
64
|
inset-block-start: 0;
|
|
65
65
|
/* Above the items (z-index 1) so scrolled rows tuck UNDER the heading (its
|
|
66
66
|
opaque bg + the ::after fade hide them), not over it; above the gliding
|
|
67
67
|
indicator (z-index 0) too. */
|
|
68
68
|
z-index: 2;
|
|
69
|
-
background: var(--page-bg);
|
|
69
|
+
background: var(--velu-sidebar-bg, var(--page-bg));
|
|
70
70
|
display: flex;
|
|
71
71
|
align-items: center;
|
|
72
72
|
gap: var(--s-2);
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
block-size: var(--s3);
|
|
96
96
|
background: linear-gradient(
|
|
97
97
|
to bottom,
|
|
98
|
-
var(--page-bg),
|
|
99
|
-
color-mix(in srgb, var(--page-bg) 55%, transparent),
|
|
98
|
+
var(--velu-sidebar-bg, var(--page-bg)),
|
|
99
|
+
color-mix(in srgb, var(--velu-sidebar-bg, var(--page-bg)) 55%, transparent),
|
|
100
100
|
transparent
|
|
101
101
|
);
|
|
102
102
|
pointer-events: none;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* Element selectors (Mintlify-compatible).
|
|
2
|
+
|
|
3
|
+
Component roots that have no semantic HTML meaning are rendered as the same
|
|
4
|
+
custom tag names Mintlify uses (`<card>`, `<callout>`, `<code-block>`, …) so
|
|
5
|
+
a project's custom CSS can target them as `card { … }`, exactly like on
|
|
6
|
+
Mintlify. Each element keeps its `velu-*` class, so the component CSS is
|
|
7
|
+
unchanged; this file only gives the unknown tags the display the `div` /
|
|
8
|
+
`span` they replaced had, and exempts them from the `* { max-width }`
|
|
9
|
+
measure reset in base.css (which lists `div` and `span` but obviously not
|
|
10
|
+
these). Class rules (specificity 0,1,0) still beat these tag rules (0,0,1).
|
|
11
|
+
|
|
12
|
+
Roots that must stay native for behaviour or accessibility (`<a>`,
|
|
13
|
+
`<button>`, `<details>`, `<summary>`, `<ul>`, `<textarea>`, headings…)
|
|
14
|
+
carry `data-component="<mintlify-name>"` instead. */
|
|
15
|
+
|
|
16
|
+
/* Block-level: replaced a <div> / <section>. */
|
|
17
|
+
accordion-group,
|
|
18
|
+
advanced-footer,
|
|
19
|
+
api-section,
|
|
20
|
+
callout,
|
|
21
|
+
card,
|
|
22
|
+
card-group,
|
|
23
|
+
chat-assistant-floating-input,
|
|
24
|
+
chat-assistant-sheet-content,
|
|
25
|
+
chat-assistant-sheet-header,
|
|
26
|
+
chat-assistant-disclaimer-text,
|
|
27
|
+
code-block,
|
|
28
|
+
code-group,
|
|
29
|
+
columns,
|
|
30
|
+
contextual-feedback-container,
|
|
31
|
+
contextual-feedback-form,
|
|
32
|
+
feedback-toolbar,
|
|
33
|
+
field,
|
|
34
|
+
mdx-content,
|
|
35
|
+
nav-tabs,
|
|
36
|
+
not-found-container,
|
|
37
|
+
not-found-status-code,
|
|
38
|
+
option-dropdown,
|
|
39
|
+
prompt,
|
|
40
|
+
sidebar-group,
|
|
41
|
+
sidebar-nav-group-divider,
|
|
42
|
+
step,
|
|
43
|
+
steps,
|
|
44
|
+
toc,
|
|
45
|
+
toc-item,
|
|
46
|
+
topbar-right-container,
|
|
47
|
+
tree,
|
|
48
|
+
tree-file,
|
|
49
|
+
update {
|
|
50
|
+
display: block;
|
|
51
|
+
max-width: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Inline: replaced a <span>. */
|
|
55
|
+
breadcrumb-item,
|
|
56
|
+
code-block-icon,
|
|
57
|
+
eyebrow,
|
|
58
|
+
method-pill,
|
|
59
|
+
nav-dropdown-item-icon,
|
|
60
|
+
nav-dropdown-item-title,
|
|
61
|
+
pagination-title,
|
|
62
|
+
sidebar-group-icon,
|
|
63
|
+
starter-question-text {
|
|
64
|
+
display: inline;
|
|
65
|
+
max-width: none;
|
|
66
|
+
}
|
|
@@ -22,11 +22,22 @@ import React from 'react';
|
|
|
22
22
|
* @param {string} [props.justify='flex-start']
|
|
23
23
|
* @param {string} [props.align='flex-start']
|
|
24
24
|
*/
|
|
25
|
+
|
|
26
|
+
// React 18 sets props on custom elements (tag names with a hyphen, e.g. the
|
|
27
|
+
// Mintlify-compatible `as="sidebar-group"`) as attributes verbatim, so
|
|
28
|
+
// `className` would come out as a literal className="" attribute. Hand those
|
|
29
|
+
// tags `class` instead. (React 19 maps className for custom elements itself,
|
|
30
|
+
// and accepts `class` too.)
|
|
31
|
+
const isCustomTag = (Tag) => typeof Tag === 'string' && Tag.includes('-');
|
|
32
|
+
const classProp = (Tag, className) =>
|
|
33
|
+
className == null ? {} : { [isCustomTag(Tag) ? 'class' : 'className']: className };
|
|
34
|
+
|
|
25
35
|
export default function Cluster({
|
|
26
36
|
as: Tag = 'div',
|
|
27
37
|
space = 'var(--s1)',
|
|
28
38
|
justify = 'flex-start',
|
|
29
39
|
align = 'flex-start',
|
|
40
|
+
className,
|
|
30
41
|
style,
|
|
31
42
|
children,
|
|
32
43
|
...rest
|
|
@@ -41,6 +52,7 @@ export default function Cluster({
|
|
|
41
52
|
gap: space,
|
|
42
53
|
...style,
|
|
43
54
|
}}
|
|
55
|
+
{...classProp(Tag, className)}
|
|
44
56
|
{...rest}
|
|
45
57
|
>
|
|
46
58
|
{children}
|