@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
|
@@ -45,9 +45,10 @@ export default function ChangelogFilters({ tags = [] }) {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
|
-
<div className="velu-changelog-filters">
|
|
48
|
+
<div id="changelog-filters" className="velu-changelog-filters">
|
|
49
49
|
<div className="velu-changelog-filters__heading">Filters</div>
|
|
50
50
|
<div
|
|
51
|
+
id="changelog-filters-content"
|
|
51
52
|
className="velu-changelog-filters__list"
|
|
52
53
|
role="group"
|
|
53
54
|
aria-label="Filter updates by tag"
|
|
@@ -336,9 +336,10 @@ function renderStream(tokens, sources, onNavigate) {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
/* ── Header ─────────────────────────────────────────────────────────── */
|
|
339
|
-
function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
|
|
339
|
+
function ChatHeader({ onClose, onNew, onHistory, historyOpen, showClose = true }) {
|
|
340
340
|
return (
|
|
341
341
|
<Cluster
|
|
342
|
+
as="chat-assistant-sheet-header"
|
|
342
343
|
space="var(--s-2)"
|
|
343
344
|
justify="space-between"
|
|
344
345
|
align="center"
|
|
@@ -369,6 +370,7 @@ function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
|
|
|
369
370
|
>
|
|
370
371
|
{resolveIcon('plus', { size: '1em' })}
|
|
371
372
|
</button>
|
|
373
|
+
{showClose && (
|
|
372
374
|
<button
|
|
373
375
|
type="button"
|
|
374
376
|
className="velu-chatbot__iconbtn"
|
|
@@ -377,6 +379,7 @@ function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
|
|
|
377
379
|
>
|
|
378
380
|
{resolveIcon('x', { size: '1em' })}
|
|
379
381
|
</button>
|
|
382
|
+
)}
|
|
380
383
|
</Cluster>
|
|
381
384
|
</Cluster>
|
|
382
385
|
);
|
|
@@ -461,7 +464,7 @@ function Welcome({ onPick, suggestions }) {
|
|
|
461
464
|
>
|
|
462
465
|
<Cluster space="var(--s-2)" align="center">
|
|
463
466
|
{resolveIcon(s.icon, { size: '1em' })}
|
|
464
|
-
<
|
|
467
|
+
<starter-question-text>{s.label}</starter-question-text>
|
|
465
468
|
</Cluster>
|
|
466
469
|
{resolveIcon('arrow-right', { size: '1em' })}
|
|
467
470
|
</button>
|
|
@@ -616,6 +619,8 @@ function Composer({ value, onChange, onSubmit, disabled }) {
|
|
|
616
619
|
<div className="velu-chatbot__input-wrap">
|
|
617
620
|
<textarea
|
|
618
621
|
ref={taRef}
|
|
622
|
+
id="chat-assistant-textarea"
|
|
623
|
+
data-component="chat-assistant-input"
|
|
619
624
|
className="velu-chatbot__input"
|
|
620
625
|
placeholder="Ask anything about the docs…"
|
|
621
626
|
rows={1}
|
|
@@ -625,6 +630,7 @@ function Composer({ value, onChange, onSubmit, disabled }) {
|
|
|
625
630
|
/>
|
|
626
631
|
<button
|
|
627
632
|
type="button"
|
|
633
|
+
data-component="chat-assistant-send-button"
|
|
628
634
|
className={`velu-chatbot__send${active ? ' is-active' : ''}`}
|
|
629
635
|
onClick={onSubmit}
|
|
630
636
|
disabled={!active}
|
|
@@ -633,9 +639,9 @@ function Composer({ value, onChange, onSubmit, disabled }) {
|
|
|
633
639
|
{resolveIcon('arrow-up', { size: '1em' })}
|
|
634
640
|
</button>
|
|
635
641
|
</div>
|
|
636
|
-
<
|
|
642
|
+
<chat-assistant-disclaimer-text class="velu-chatbot__foot">
|
|
637
643
|
<kbd>Enter</kbd> to send · <kbd>⇧ Enter</kbd> for newline
|
|
638
|
-
</
|
|
644
|
+
</chat-assistant-disclaimer-text>
|
|
639
645
|
</div>
|
|
640
646
|
);
|
|
641
647
|
}
|
|
@@ -651,6 +657,7 @@ export default function Chatbot({
|
|
|
651
657
|
listHistory,
|
|
652
658
|
loadConversation,
|
|
653
659
|
suggestions,
|
|
660
|
+
layout = 'panel',
|
|
654
661
|
className = '',
|
|
655
662
|
...rest
|
|
656
663
|
}) {
|
|
@@ -878,8 +885,9 @@ export default function Chatbot({
|
|
|
878
885
|
const isBottomSheet = () =>
|
|
879
886
|
typeof window !== 'undefined' &&
|
|
880
887
|
window.matchMedia('(max-width: 1024px)').matches;
|
|
888
|
+
const isPage = layout === 'page';
|
|
881
889
|
const onDragStart = (e) => {
|
|
882
|
-
if (!isBottomSheet()) return;
|
|
890
|
+
if (isPage || !isBottomSheet()) return;
|
|
883
891
|
dragRef.current = { active: true, startY: e.clientY, currentY: 0 };
|
|
884
892
|
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
885
893
|
};
|
|
@@ -902,7 +910,7 @@ export default function Chatbot({
|
|
|
902
910
|
if (!open) setDragOffset(0);
|
|
903
911
|
}, [open]);
|
|
904
912
|
|
|
905
|
-
const cls = `velu-chatbot${open ? ' velu-chatbot--open' : ''} ${className}`.trim();
|
|
913
|
+
const cls = `velu-chatbot${open || isPage ? ' velu-chatbot--open' : ''}${isPage ? ' velu-chatbot--page' : ''} ${className}`.trim();
|
|
906
914
|
const dragStyle =
|
|
907
915
|
dragOffset > 0
|
|
908
916
|
? { transform: `translateY(${dragOffset}px)`, transition: 'none' }
|
|
@@ -910,8 +918,10 @@ export default function Chatbot({
|
|
|
910
918
|
|
|
911
919
|
return (
|
|
912
920
|
<aside
|
|
921
|
+
id="chat-assistant-sheet"
|
|
922
|
+
data-component="chat-assistant-sheet"
|
|
913
923
|
className={cls}
|
|
914
|
-
aria-hidden={!open}
|
|
924
|
+
aria-hidden={isPage ? false : !open}
|
|
915
925
|
aria-label="Ask AI"
|
|
916
926
|
style={dragStyle}
|
|
917
927
|
{...rest}
|
|
@@ -933,6 +943,7 @@ export default function Chatbot({
|
|
|
933
943
|
onNew={newChat}
|
|
934
944
|
onHistory={() => setHistoryOpen((o) => !o)}
|
|
935
945
|
historyOpen={historyOpen}
|
|
946
|
+
showClose={!isPage}
|
|
936
947
|
/>
|
|
937
948
|
|
|
938
949
|
{historyOpen && (
|
|
@@ -944,7 +955,7 @@ export default function Chatbot({
|
|
|
944
955
|
/>
|
|
945
956
|
)}
|
|
946
957
|
|
|
947
|
-
<
|
|
958
|
+
<chat-assistant-sheet-content class="velu-chatbot__body" ref={bodyRef}>
|
|
948
959
|
{messages.length === 0 ? (
|
|
949
960
|
<Welcome onPick={(t) => send(t)} suggestions={suggestions} />
|
|
950
961
|
) : (
|
|
@@ -968,7 +979,7 @@ export default function Chatbot({
|
|
|
968
979
|
)}
|
|
969
980
|
</Stack>
|
|
970
981
|
)}
|
|
971
|
-
</
|
|
982
|
+
</chat-assistant-sheet-content>
|
|
972
983
|
|
|
973
984
|
<Composer
|
|
974
985
|
value={input}
|
|
@@ -90,9 +90,9 @@ function TabLabel({ icon, label }) {
|
|
|
90
90
|
return (
|
|
91
91
|
<>
|
|
92
92
|
{icon != null && (
|
|
93
|
-
<
|
|
93
|
+
<code-block-icon class="velu-code-block__tab-icon" aria-hidden="true">
|
|
94
94
|
{typeof icon === 'string' ? resolveIcon(icon, { size: '1em' }) : icon}
|
|
95
|
-
</
|
|
95
|
+
</code-block-icon>
|
|
96
96
|
)}
|
|
97
97
|
<span className="velu-code-block__tab-label">{label}</span>
|
|
98
98
|
</>
|
|
@@ -143,6 +143,7 @@ function CopyButton({ code }) {
|
|
|
143
143
|
type="button"
|
|
144
144
|
onClick={copy}
|
|
145
145
|
className="velu-code-block__copy"
|
|
146
|
+
data-component="code-block-copy-button"
|
|
146
147
|
aria-label={copied ? 'Copied' : 'Copy code'}
|
|
147
148
|
title={copied ? 'Copied' : 'Copy'}
|
|
148
149
|
>
|
|
@@ -242,7 +243,7 @@ export function CodeBlock({
|
|
|
242
243
|
const label = filename || title;
|
|
243
244
|
const tabIcon = pickIcon({ icon, withIcon, language });
|
|
244
245
|
return (
|
|
245
|
-
<
|
|
246
|
+
<code-block class={cls} {...rest}>
|
|
246
247
|
{label && (
|
|
247
248
|
<div className="velu-code-block__header">
|
|
248
249
|
<Tab active icon={tabIcon} label={label} />
|
|
@@ -254,7 +255,7 @@ export function CodeBlock({
|
|
|
254
255
|
lineNumbers={lineNumbers}
|
|
255
256
|
highlight={highlight}
|
|
256
257
|
/>
|
|
257
|
-
</
|
|
258
|
+
</code-block>
|
|
258
259
|
);
|
|
259
260
|
}
|
|
260
261
|
CodeBlock.displayName = 'CodeBlock';
|
|
@@ -340,7 +341,7 @@ export function CodeGroup({
|
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
return (
|
|
343
|
-
<
|
|
344
|
+
<code-group class={cls} {...rest}>
|
|
344
345
|
<div className="velu-code-block__header">
|
|
345
346
|
{canLeft && (
|
|
346
347
|
<button
|
|
@@ -391,7 +392,7 @@ export function CodeGroup({
|
|
|
391
392
|
lineNumbers={lineNumbers}
|
|
392
393
|
highlight={highlight}
|
|
393
394
|
/>
|
|
394
|
-
</
|
|
395
|
+
</code-group>
|
|
395
396
|
);
|
|
396
397
|
}
|
|
397
398
|
|
|
@@ -44,10 +44,12 @@ export default function Columns({
|
|
|
44
44
|
}) {
|
|
45
45
|
return (
|
|
46
46
|
<Switcher
|
|
47
|
+
as="columns"
|
|
47
48
|
className={`velu-columns ${className}`.trim()}
|
|
48
49
|
space={space}
|
|
49
50
|
min={min}
|
|
50
51
|
limit={cols}
|
|
52
|
+
style={{ '--vepa-columns': Math.max(1, Math.min(4, Number(cols) || 2)) }}
|
|
51
53
|
{...rest}
|
|
52
54
|
>
|
|
53
55
|
{children}
|
|
@@ -256,11 +256,11 @@ export default function ContextMenu({
|
|
|
256
256
|
className="velu-context-bar"
|
|
257
257
|
data-pagefind-ignore=""
|
|
258
258
|
>
|
|
259
|
-
{eyebrow ? <
|
|
259
|
+
{eyebrow ? <eyebrow className="velu-context-bar__eyebrow">{eyebrow}</eyebrow> : <span />}
|
|
260
260
|
|
|
261
261
|
<div className="velu-context-bar__actions">
|
|
262
262
|
{items.length > 0 && (
|
|
263
|
-
<div ref={rootRef} className="velu-context-menu" data-open={open ? 'true' : 'false'}>
|
|
263
|
+
<div ref={rootRef} id="page-context-menu" className="velu-context-menu" data-open={open ? 'true' : 'false'}>
|
|
264
264
|
<div className="velu-context-menu__split">
|
|
265
265
|
<button
|
|
266
266
|
type="button"
|
|
@@ -275,6 +275,7 @@ export default function ContextMenu({
|
|
|
275
275
|
</button>
|
|
276
276
|
<button
|
|
277
277
|
type="button"
|
|
278
|
+
id="page-context-menu-button"
|
|
278
279
|
className="velu-context-menu__toggle"
|
|
279
280
|
aria-haspopup="menu"
|
|
280
281
|
aria-expanded={open}
|
|
@@ -46,7 +46,7 @@ export default function Field({
|
|
|
46
46
|
}) {
|
|
47
47
|
const hasDefault = defaultValue !== undefined && defaultValue !== null;
|
|
48
48
|
return (
|
|
49
|
-
<
|
|
49
|
+
<field className={`velu-field ${className}`.trim()} {...rest}>
|
|
50
50
|
{/* Cluster handles the wrap-friendly inline row layout — chips +
|
|
51
51
|
name + type + required + default + post — with a 16px gap and
|
|
52
52
|
baseline alignment so the bold name doesn't shift vertically
|
|
@@ -76,6 +76,6 @@ export default function Field({
|
|
|
76
76
|
{children != null && children !== false && (
|
|
77
77
|
<div className="velu-field__body">{children}</div>
|
|
78
78
|
)}
|
|
79
|
-
</
|
|
79
|
+
</field>
|
|
80
80
|
);
|
|
81
81
|
}
|
|
@@ -149,14 +149,14 @@ export default function Image({
|
|
|
149
149
|
|
|
150
150
|
if (caption) {
|
|
151
151
|
return (
|
|
152
|
-
<figure className={cls} {...rest}>
|
|
152
|
+
<figure className={cls} data-component={chrome === 'frame' ? 'frame' : undefined} {...rest}>
|
|
153
153
|
{body}
|
|
154
154
|
<figcaption className="velu-image__caption">{caption}</figcaption>
|
|
155
155
|
</figure>
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
return (
|
|
159
|
-
<div className={cls} {...rest}>
|
|
159
|
+
<div className={cls} data-component={chrome === 'frame' ? 'frame' : undefined} {...rest}>
|
|
160
160
|
{body}
|
|
161
161
|
</div>
|
|
162
162
|
);
|
|
@@ -24,8 +24,8 @@ export default function MethodBadge({
|
|
|
24
24
|
const safe = METHODS.includes(key) ? key : 'get';
|
|
25
25
|
const cls = `velu-method-badge velu-method-badge--${safe} ${className}`.trim();
|
|
26
26
|
return (
|
|
27
|
-
<
|
|
27
|
+
<method-pill class={cls} {...rest}>
|
|
28
28
|
{String(method).toUpperCase()}
|
|
29
|
-
</
|
|
29
|
+
</method-pill>
|
|
30
30
|
);
|
|
31
31
|
}
|
|
@@ -30,9 +30,16 @@ export default function NavSelect({
|
|
|
30
30
|
bare = false,
|
|
31
31
|
linkComponent = 'a',
|
|
32
32
|
className = '',
|
|
33
|
+
// Mintlify-compatible hooks: id prefix for the trigger/menu
|
|
34
|
+
// (e.g. 'localization-select' → #localization-select-trigger /
|
|
35
|
+
// #localization-select-content / #localization-select-item) and the
|
|
36
|
+
// data-component family ('nav-dropdown' → nav-dropdown-trigger, …).
|
|
37
|
+
idPrefix,
|
|
38
|
+
component = 'nav-dropdown',
|
|
33
39
|
...rest
|
|
34
40
|
}) {
|
|
35
41
|
const Link = linkComponent;
|
|
42
|
+
const hookId = (suffix) => (idPrefix ? `${idPrefix}-${suffix}` : undefined);
|
|
36
43
|
const [open, setOpen] = React.useState(false);
|
|
37
44
|
const rootRef = React.useRef(null);
|
|
38
45
|
|
|
@@ -61,6 +68,8 @@ export default function NavSelect({
|
|
|
61
68
|
>
|
|
62
69
|
<button
|
|
63
70
|
type="button"
|
|
71
|
+
id={hookId('trigger')}
|
|
72
|
+
data-component={`${component}-trigger`}
|
|
64
73
|
className="velu-nav-select__btn"
|
|
65
74
|
aria-haspopup="menu"
|
|
66
75
|
aria-expanded={open}
|
|
@@ -82,23 +91,32 @@ export default function NavSelect({
|
|
|
82
91
|
focusable="false"
|
|
83
92
|
/>
|
|
84
93
|
</button>
|
|
85
|
-
<ul
|
|
94
|
+
<ul
|
|
95
|
+
id={hookId('content')}
|
|
96
|
+
data-component={`${component}-content`}
|
|
97
|
+
className="velu-nav-select__menu"
|
|
98
|
+
role="menu"
|
|
99
|
+
aria-hidden={!open}
|
|
100
|
+
>
|
|
86
101
|
{options.map((o, i) => (
|
|
87
102
|
<li key={o.href ?? i} role="none">
|
|
88
103
|
<Link
|
|
89
104
|
role="menuitem"
|
|
105
|
+
id={i === 0 ? hookId('item') : undefined}
|
|
106
|
+
data-component={`${component}-item`}
|
|
107
|
+
data-active={o.active ? 'true' : undefined}
|
|
90
108
|
className={`velu-nav-select__item${o.active ? ' velu-nav-select__item--active' : ''}`}
|
|
91
109
|
href={o.href}
|
|
92
110
|
tabIndex={open ? 0 : -1}
|
|
93
111
|
onClick={() => setOpen(false)}
|
|
94
112
|
>
|
|
95
113
|
{o.icon && (
|
|
96
|
-
<
|
|
114
|
+
<nav-dropdown-item-icon class="velu-nav-select__item-icon" aria-hidden="true">
|
|
97
115
|
{resolveIcon(o.icon, { size: '1em' })}
|
|
98
|
-
</
|
|
116
|
+
</nav-dropdown-item-icon>
|
|
99
117
|
)}
|
|
100
118
|
{o.code && <span className="velu-nav-select__code">{o.code}</span>}
|
|
101
|
-
<
|
|
119
|
+
<nav-dropdown-item-title>{o.label}</nav-dropdown-item-title>
|
|
102
120
|
</Link>
|
|
103
121
|
</li>
|
|
104
122
|
))}
|
|
@@ -27,15 +27,15 @@ export default function NotFound({
|
|
|
27
27
|
}) {
|
|
28
28
|
const Link = linkComponent;
|
|
29
29
|
return (
|
|
30
|
-
<
|
|
30
|
+
<not-found-container class="velu-404">
|
|
31
31
|
<p className="velu-404__eyebrow">{eyebrow}</p>
|
|
32
|
-
<
|
|
32
|
+
<not-found-status-code class="velu-404__num" aria-hidden="true">
|
|
33
33
|
404
|
|
34
|
-
</
|
|
35
|
-
<h1 className="velu-404__title">{title}</h1>
|
|
36
|
-
<p className="velu-404__body">{body}</p>
|
|
34
|
+
</not-found-status-code>
|
|
35
|
+
<h1 className="velu-404__title" data-component="not-found-title">{title}</h1>
|
|
36
|
+
<p className="velu-404__body" data-component="not-found-description">{body}</p>
|
|
37
37
|
<div className="velu-404__actions">
|
|
38
|
-
<Link className="velu-404__btn velu-404__btn--primary" href={homeHref}>
|
|
38
|
+
<Link className="velu-404__btn velu-404__btn--primary" data-component="not-found-recommended-page-link" href={homeHref}>
|
|
39
39
|
<span className="velu-404__btn-ic" aria-hidden="true">
|
|
40
40
|
{resolveIcon('house', { size: '1em' })}
|
|
41
41
|
</span>
|
|
@@ -58,6 +58,6 @@ export default function NotFound({
|
|
|
58
58
|
</button>
|
|
59
59
|
)}
|
|
60
60
|
</div>
|
|
61
|
-
</
|
|
61
|
+
</not-found-container>
|
|
62
62
|
);
|
|
63
63
|
}
|
|
@@ -110,16 +110,19 @@ export default function PageFeedback({
|
|
|
110
110
|
|
|
111
111
|
return (
|
|
112
112
|
<Stack
|
|
113
|
+
as="contextual-feedback-container"
|
|
113
114
|
space="var(--s0)"
|
|
114
115
|
className={`velu-feedback ${className}`.trim()}
|
|
115
116
|
{...rest}
|
|
116
117
|
>
|
|
117
|
-
<Cluster space="var(--s0)" align="center" className="velu-feedback__bar">
|
|
118
|
+
<Cluster as="feedback-toolbar" space="var(--s0)" align="center" className="velu-feedback__bar">
|
|
118
119
|
<span className="velu-feedback__question">{question}</span>
|
|
119
120
|
|
|
120
121
|
<Cluster space="var(--s-2)" align="center">
|
|
121
122
|
<button
|
|
122
123
|
type="button"
|
|
124
|
+
id="feedback-thumbs-up"
|
|
125
|
+
data-component="contextual-feedback-button"
|
|
123
126
|
className={`velu-feedback__btn${
|
|
124
127
|
vote === 'yes' ? ' velu-feedback__btn--chosen' : ''
|
|
125
128
|
}`}
|
|
@@ -132,6 +135,8 @@ export default function PageFeedback({
|
|
|
132
135
|
</button>
|
|
133
136
|
<button
|
|
134
137
|
type="button"
|
|
138
|
+
id="feedback-thumbs-down"
|
|
139
|
+
data-component="contextual-feedback-button"
|
|
135
140
|
className={`velu-feedback__btn${
|
|
136
141
|
vote === 'no' ? ' velu-feedback__btn--chosen' : ''
|
|
137
142
|
}`}
|
|
@@ -151,8 +156,8 @@ export default function PageFeedback({
|
|
|
151
156
|
</Cluster>
|
|
152
157
|
|
|
153
158
|
{showForm && (
|
|
154
|
-
<Stack space="var(--s1)" className="velu-feedback__form">
|
|
155
|
-
<h3 className="velu-feedback__form-title">
|
|
159
|
+
<Stack as="contextual-feedback-form" id="feedback-form" space="var(--s1)" className="velu-feedback__form">
|
|
160
|
+
<h3 className="velu-feedback__form-title" data-component="contextual-feedback-form-title">
|
|
156
161
|
How can we improve our product?
|
|
157
162
|
</h3>
|
|
158
163
|
|
|
@@ -176,6 +181,8 @@ export default function PageFeedback({
|
|
|
176
181
|
</Stack>
|
|
177
182
|
|
|
178
183
|
<textarea
|
|
184
|
+
id="feedback-form-input"
|
|
185
|
+
data-component="contextual-feedback-input"
|
|
179
186
|
className="velu-feedback__detail"
|
|
180
187
|
placeholder="(optional) Could you share more about your experience?"
|
|
181
188
|
value={detail}
|
|
@@ -186,6 +193,7 @@ export default function PageFeedback({
|
|
|
186
193
|
<Cluster space="var(--s-1)">
|
|
187
194
|
<button
|
|
188
195
|
type="button"
|
|
196
|
+
id="feedback-form-cancel"
|
|
189
197
|
className="velu-feedback__action velu-feedback__action--cancel"
|
|
190
198
|
onClick={cancel}
|
|
191
199
|
>
|
|
@@ -193,6 +201,8 @@ export default function PageFeedback({
|
|
|
193
201
|
</button>
|
|
194
202
|
<button
|
|
195
203
|
type="button"
|
|
204
|
+
id="feedback-form-submit"
|
|
205
|
+
data-component="contextual-feedback-form-submit-button"
|
|
196
206
|
className="velu-feedback__action velu-feedback__action--submit"
|
|
197
207
|
onClick={submit}
|
|
198
208
|
>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import VepaFooter from './VepaFooter.jsx';
|
|
2
3
|
import Stack from '../primitives/Stack.jsx';
|
|
3
4
|
import Cluster from '../primitives/Cluster.jsx';
|
|
4
5
|
import SocialLinks from './SocialLinks.jsx';
|
|
@@ -67,6 +68,7 @@ function VeluMark() {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
export default function PageFooter({
|
|
71
|
+
preset,
|
|
70
72
|
brand,
|
|
71
73
|
columns = [],
|
|
72
74
|
socials = [],
|
|
@@ -75,6 +77,7 @@ export default function PageFooter({
|
|
|
75
77
|
...rest
|
|
76
78
|
}) {
|
|
77
79
|
const Link = linkComponent;
|
|
80
|
+
if (preset === 'vepa') return <VepaFooter brand={brand} columns={columns} socials={socials} linkComponent={Link} />;
|
|
78
81
|
const brandLabel = brand?.label ?? 'Velu';
|
|
79
82
|
const brandHref = brand?.href;
|
|
80
83
|
|
|
@@ -68,9 +68,13 @@ export function VeluMark() {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function ActionButton({ action, Link }) {
|
|
72
|
-
const { label, icon, kind = 'outlined', href, onClick, external } = action;
|
|
71
|
+
function ActionButton({ action, Link, id }) {
|
|
72
|
+
const { label, icon, kind = 'outlined', href, onClick, external, component } = action;
|
|
73
73
|
const cls = `velu-header__action velu-header__action--${kind}`;
|
|
74
|
+
// Mintlify-compatible hooks: `#topbar-cta-button` on the first primary
|
|
75
|
+
// action, `[data-component="navbar-link"]` on every action (overridable per
|
|
76
|
+
// action, e.g. component: 'logout-link').
|
|
77
|
+
const hooks = { id, 'data-component': component ?? 'navbar-link', 'data-component-name': kind === 'primary' ? 'primary-header-button' : undefined };
|
|
74
78
|
const content = (
|
|
75
79
|
<>
|
|
76
80
|
{icon && (
|
|
@@ -87,13 +91,13 @@ function ActionButton({ action, Link }) {
|
|
|
87
91
|
? { target: '_blank', rel: 'noreferrer' }
|
|
88
92
|
: {};
|
|
89
93
|
return (
|
|
90
|
-
<Tag className={cls} href={href} {...extraProps}>
|
|
94
|
+
<Tag className={cls} href={href} {...hooks} {...extraProps}>
|
|
91
95
|
{content}
|
|
92
96
|
</Tag>
|
|
93
97
|
);
|
|
94
98
|
}
|
|
95
99
|
return (
|
|
96
|
-
<button type="button" className={cls} onClick={onClick}>
|
|
100
|
+
<button type="button" className={cls} onClick={onClick} {...hooks}>
|
|
97
101
|
{content}
|
|
98
102
|
</button>
|
|
99
103
|
);
|
|
@@ -111,6 +115,8 @@ function HeaderTab({ tab, isActive, Link }) {
|
|
|
111
115
|
className={cls}
|
|
112
116
|
href={href}
|
|
113
117
|
aria-current={isActive ? 'page' : undefined}
|
|
118
|
+
data-active={isActive ? 'true' : undefined}
|
|
119
|
+
data-component="nav-tabs-item"
|
|
114
120
|
{...extraProps}
|
|
115
121
|
>
|
|
116
122
|
{icon && (
|
|
@@ -226,6 +232,7 @@ function KebabMenu({ actions, Link }) {
|
|
|
226
232
|
}
|
|
227
233
|
|
|
228
234
|
export default function PageHeader({
|
|
235
|
+
preset,
|
|
229
236
|
brand,
|
|
230
237
|
brandTrailing,
|
|
231
238
|
center,
|
|
@@ -243,6 +250,7 @@ export default function PageHeader({
|
|
|
243
250
|
const Link = linkComponent;
|
|
244
251
|
const brandLabel = brand?.label ?? 'Velu';
|
|
245
252
|
const brandHref = brand?.href;
|
|
253
|
+
const firstPrimary = actions.findIndex((a) => a.kind === 'primary');
|
|
246
254
|
|
|
247
255
|
// Track whether the page has been scrolled so the header can flip
|
|
248
256
|
// from solid to translucent / frosted. Edge-triggered (only updates
|
|
@@ -259,6 +267,12 @@ export default function PageHeader({
|
|
|
259
267
|
return () => window.removeEventListener('scroll', onScroll);
|
|
260
268
|
}, []);
|
|
261
269
|
|
|
270
|
+
const showTabsRow =
|
|
271
|
+
tabs.length > 0 ||
|
|
272
|
+
(breadcrumb && breadcrumb.length > 0) ||
|
|
273
|
+
Boolean(tabsTrailing) ||
|
|
274
|
+
Boolean(onMenuClick);
|
|
275
|
+
|
|
262
276
|
// A configured logo replaces the mark + wordmark entirely (Mintlify-style).
|
|
263
277
|
const brandBlock = (
|
|
264
278
|
<Cluster space="var(--s-3)" align="center" className="velu-header__brand" style={{ flexWrap: 'nowrap' }}>
|
|
@@ -276,8 +290,11 @@ export default function PageHeader({
|
|
|
276
290
|
return (
|
|
277
291
|
<Stack
|
|
278
292
|
as="header"
|
|
279
|
-
space=
|
|
280
|
-
|
|
293
|
+
space={preset === 'vepa' ? '0px' : 'var(--s0)'}
|
|
294
|
+
data-has-tabs={tabs.length > 0 ? 'true' : 'false'}
|
|
295
|
+
className={`velu-header${scrolled ? ' velu-header--scrolled' : ''}${
|
|
296
|
+
showTabsRow ? '' : ' velu-header--no-tabs'
|
|
297
|
+
} ${className}`.trim()}
|
|
281
298
|
{...rest}
|
|
282
299
|
>
|
|
283
300
|
{/* Top row — 3-column grid: brand | center | actions. */}
|
|
@@ -288,7 +305,7 @@ export default function PageHeader({
|
|
|
288
305
|
className="velu-header__col velu-header__col--brand"
|
|
289
306
|
>
|
|
290
307
|
{brandHref ? (
|
|
291
|
-
<Link href={brandHref} className="velu-header__brand-link">
|
|
308
|
+
<Link href={brandHref} className="velu-header__brand-link" data-component="nav-logo">
|
|
292
309
|
{brandBlock}
|
|
293
310
|
</Link>
|
|
294
311
|
) : (
|
|
@@ -306,6 +323,7 @@ export default function PageHeader({
|
|
|
306
323
|
|
|
307
324
|
{(actions.length > 0 || trailing) && (
|
|
308
325
|
<Cluster
|
|
326
|
+
as="topbar-right-container"
|
|
309
327
|
space="var(--s-2)"
|
|
310
328
|
align="center"
|
|
311
329
|
className="velu-header__col velu-header__col--actions"
|
|
@@ -316,7 +334,12 @@ export default function PageHeader({
|
|
|
316
334
|
{actions.length > 0 && (
|
|
317
335
|
<div className="velu-header__actions-list">
|
|
318
336
|
{actions.map((a, i) => (
|
|
319
|
-
<ActionButton
|
|
337
|
+
<ActionButton
|
|
338
|
+
key={i}
|
|
339
|
+
action={a}
|
|
340
|
+
Link={Link}
|
|
341
|
+
id={i === firstPrimary ? 'topbar-cta-button' : undefined}
|
|
342
|
+
/>
|
|
320
343
|
))}
|
|
321
344
|
</div>
|
|
322
345
|
)}
|
|
@@ -336,11 +359,9 @@ export default function PageHeader({
|
|
|
336
359
|
takes their place. Burger stays at the trailing edge via
|
|
337
360
|
margin-inline-start: auto on the burger button itself, so
|
|
338
361
|
its position is unaffected by the breadcrumb's length. */}
|
|
339
|
-
{
|
|
340
|
-
(breadcrumb && breadcrumb.length > 0) ||
|
|
341
|
-
tabsTrailing ||
|
|
342
|
-
onMenuClick) && (
|
|
362
|
+
{showTabsRow && (
|
|
343
363
|
<Cluster
|
|
364
|
+
as="nav-tabs"
|
|
344
365
|
space="var(--s0)"
|
|
345
366
|
align="center"
|
|
346
367
|
className="velu-header__tabs"
|
|
@@ -367,6 +388,7 @@ export default function PageHeader({
|
|
|
367
388
|
<button
|
|
368
389
|
type="button"
|
|
369
390
|
className="velu-header__crumbs"
|
|
391
|
+
data-component="breadcrumb-list"
|
|
370
392
|
onClick={onMenuClick}
|
|
371
393
|
aria-label="Open navigation menu"
|
|
372
394
|
>
|
|
@@ -381,13 +403,13 @@ export default function PageHeader({
|
|
|
381
403
|
focusable="false"
|
|
382
404
|
/>
|
|
383
405
|
)}
|
|
384
|
-
<
|
|
385
|
-
|
|
406
|
+
<breadcrumb-item
|
|
407
|
+
class={`velu-header__crumb${
|
|
386
408
|
isLast ? ' velu-header__crumb--current' : ''
|
|
387
409
|
}`}
|
|
388
410
|
>
|
|
389
411
|
{c.label}
|
|
390
|
-
</
|
|
412
|
+
</breadcrumb-item>
|
|
391
413
|
</React.Fragment>
|
|
392
414
|
);
|
|
393
415
|
})}
|
|
@@ -32,9 +32,10 @@ function NavCard({ page, dir, Link }) {
|
|
|
32
32
|
return (
|
|
33
33
|
<Link
|
|
34
34
|
className={`velu-pagenav__card velu-pagenav__card--${dir}`}
|
|
35
|
+
data-component={`pagination-${dir}`}
|
|
35
36
|
href={page.href ?? '#'}
|
|
36
37
|
>
|
|
37
|
-
<
|
|
38
|
+
<pagination-title class="velu-pagenav__title">{page.label}</pagination-title>
|
|
38
39
|
<Cluster
|
|
39
40
|
space="var(--s-4)"
|
|
40
41
|
align="center"
|
|
@@ -69,7 +69,7 @@ export default function Prompt({
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
|
-
<
|
|
72
|
+
<prompt className={`velu-prompt ${className}`.trim()} {...rest}>
|
|
73
73
|
{title != null && (
|
|
74
74
|
<div className="velu-prompt__title">{title}</div>
|
|
75
75
|
)}
|
|
@@ -110,6 +110,6 @@ export default function Prompt({
|
|
|
110
110
|
{copied ? 'Copied' : 'Copy Prompt'}
|
|
111
111
|
</button>
|
|
112
112
|
</Cluster>
|
|
113
|
-
</
|
|
113
|
+
</prompt>
|
|
114
114
|
);
|
|
115
115
|
}
|