@veluai/velu 0.2.13 → 0.2.15

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 (109) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +37 -37
  3. package/package.json +64 -64
  4. package/runtime/velu-ui/base.css +320 -320
  5. package/runtime/velu-ui/components/Accordion.jsx +64 -64
  6. package/runtime/velu-ui/components/ApiClient.jsx +207 -207
  7. package/runtime/velu-ui/components/ApiField.jsx +87 -87
  8. package/runtime/velu-ui/components/ApiPath.jsx +63 -63
  9. package/runtime/velu-ui/components/ApiReferencePage.jsx +384 -384
  10. package/runtime/velu-ui/components/ApiSamples.jsx +36 -36
  11. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -122
  12. package/runtime/velu-ui/components/AskBar.jsx +71 -71
  13. package/runtime/velu-ui/components/Callout.jsx +114 -114
  14. package/runtime/velu-ui/components/Card.jsx +131 -131
  15. package/runtime/velu-ui/components/Chatbot.jsx +885 -885
  16. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  17. package/runtime/velu-ui/components/Columns.jsx +56 -56
  18. package/runtime/velu-ui/components/ContextMenu.jsx +298 -273
  19. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  20. package/runtime/velu-ui/components/Field.jsx +81 -81
  21. package/runtime/velu-ui/components/Image.jsx +163 -163
  22. package/runtime/velu-ui/components/Logo.jsx +31 -31
  23. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  24. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  25. package/runtime/velu-ui/components/NotFound.jsx +63 -63
  26. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  27. package/runtime/velu-ui/components/PageFooter.jsx +145 -145
  28. package/runtime/velu-ui/components/PageHeader.jsx +422 -422
  29. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  30. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  31. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  32. package/runtime/velu-ui/components/Search.jsx +460 -460
  33. package/runtime/velu-ui/components/Sidebar.jsx +254 -254
  34. package/runtime/velu-ui/components/SocialLinks.jsx +90 -90
  35. package/runtime/velu-ui/components/Steps.jsx +65 -65
  36. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  37. package/runtime/velu-ui/components/Toc.jsx +537 -537
  38. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  39. package/runtime/velu-ui/components/Tree.jsx +87 -87
  40. package/runtime/velu-ui/components/TryItBar.jsx +102 -102
  41. package/runtime/velu-ui/components/accordion.css +92 -92
  42. package/runtime/velu-ui/components/api-page.css +208 -208
  43. package/runtime/velu-ui/components/api.css +635 -635
  44. package/runtime/velu-ui/components/ask-bar.css +94 -94
  45. package/runtime/velu-ui/components/card.css +105 -105
  46. package/runtime/velu-ui/components/chatbot.css +622 -622
  47. package/runtime/velu-ui/components/code-block.css +263 -263
  48. package/runtime/velu-ui/components/context-menu.css +173 -173
  49. package/runtime/velu-ui/components/docs-layout.css +822 -822
  50. package/runtime/velu-ui/components/field.css +82 -82
  51. package/runtime/velu-ui/components/image.css +237 -237
  52. package/runtime/velu-ui/components/nav-select.css +157 -157
  53. package/runtime/velu-ui/components/not-found.css +94 -94
  54. package/runtime/velu-ui/components/page-feedback.css +241 -241
  55. package/runtime/velu-ui/components/page-footer.css +130 -130
  56. package/runtime/velu-ui/components/page-header.css +558 -558
  57. package/runtime/velu-ui/components/page-nav.css +50 -50
  58. package/runtime/velu-ui/components/powered-by.css +92 -92
  59. package/runtime/velu-ui/components/prompt.css +99 -99
  60. package/runtime/velu-ui/components/search.css +307 -307
  61. package/runtime/velu-ui/components/sidebar.css +205 -205
  62. package/runtime/velu-ui/components/steps.css +77 -77
  63. package/runtime/velu-ui/components/theme-toggle.css +102 -102
  64. package/runtime/velu-ui/components/toc-bar.css +234 -234
  65. package/runtime/velu-ui/components/tree.css +49 -49
  66. package/runtime/velu-ui/index.js +54 -54
  67. package/runtime/velu-ui/lib/api-send.js +92 -92
  68. package/runtime/velu-ui/lib/brand-icons.jsx +103 -103
  69. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  70. package/runtime/velu-ui/lib/copyText.js +64 -64
  71. package/runtime/velu-ui/lib/docs-assistant.js +250 -250
  72. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  73. package/runtime/velu-ui/lib/pagefind.js +113 -113
  74. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  75. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  76. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  77. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  78. package/runtime/velu-ui/mdx-components.jsx +105 -105
  79. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  80. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  81. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  82. package/runtime/velu-ui/primitives/stack.css +3 -3
  83. package/runtime/velu-ui/primitives/switcher.css +25 -25
  84. package/runtime/velu-ui/styles.css +46 -46
  85. package/runtime/velu-ui/tokens.css +4 -4
  86. package/schema/velu.schema.json +423 -423
  87. package/src/lib/extract-mdx-error.js +170 -170
  88. package/src/lib/issues.js +159 -159
  89. package/src/lib/known-components.js +34 -34
  90. package/src/navigation.js +443 -443
  91. package/src/runtime/App.jsx +1669 -1668
  92. package/src/runtime/ErrorBoundary.jsx +54 -54
  93. package/src/runtime/client-entry.jsx +22 -22
  94. package/src/runtime/server-entry.jsx +16 -16
  95. package/src/template.html +48 -48
  96. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  97. package/templates/starter/ai-tools/cursor.mdx +17 -17
  98. package/templates/starter/api-reference/introduction.mdx +43 -43
  99. package/templates/starter/development.mdx +19 -19
  100. package/templates/starter/essentials/code.mdx +29 -29
  101. package/templates/starter/essentials/images.mdx +29 -29
  102. package/templates/starter/essentials/markdown.mdx +25 -25
  103. package/templates/starter/essentials/navigation.mdx +39 -39
  104. package/templates/starter/essentials/settings.mdx +30 -30
  105. package/templates/starter/favicon.svg +6 -6
  106. package/templates/starter/index.mdx +31 -31
  107. package/templates/starter/openapi.json +160 -160
  108. package/templates/starter/quickstart.mdx +31 -31
  109. package/templates/starter/velu.json +41 -41
@@ -1,64 +1,64 @@
1
- import React from 'react';
2
- import resolveIcon from '../lib/resolveIcon.jsx';
3
-
4
- /**
5
- * Accordion + AccordionGroup — collapsible disclosure.
6
- *
7
- * Native <details>/<summary>: zero JS, SSR-safe, accessible, chevron rotates
8
- * via CSS on [open] (same pattern as Sidebar). Fully tokenized — border /
9
- * hover / expanded surface / radius / spacing / type all from base.css
10
- * tokens, so light + dark theme automatically via [data-theme].
11
- *
12
- * - <Accordion title=… defaultOpen?>content</Accordion> — standalone bordered
13
- * rounded box.
14
- * - <AccordionGroup><Accordion/>…</AccordionGroup> — items joined into ONE
15
- * bordered box with dividers between them (no per-item border/radius). A
16
- * context tells nested Accordions they're grouped so they drop their own
17
- * chrome; the group container owns the border + radius + dividers.
18
- *
19
- * @param {{ title: React.ReactNode, defaultOpen?: boolean,
20
- * children: React.ReactNode, className?: string }} props
21
- */
22
-
23
- const GroupCtx = React.createContext(false);
24
-
25
- export function Accordion({
26
- title,
27
- defaultOpen = false,
28
- children,
29
- className = '',
30
- ...rest
31
- }) {
32
- const inGroup = React.useContext(GroupCtx);
33
- const cls = [
34
- 'velu-accordion',
35
- inGroup ? 'velu-accordion--in-group' : '',
36
- className,
37
- ]
38
- .filter(Boolean)
39
- .join(' ');
40
-
41
- return (
42
- <details className={cls} open={defaultOpen || undefined} {...rest}>
43
- <summary className="velu-accordion__summary">
44
- <span className="velu-accordion__chevron" aria-hidden="true">
45
- {resolveIcon('chevron-right', { size: '1em' })}
46
- </span>
47
- <span className="velu-accordion__title">{title}</span>
48
- </summary>
49
- <div className="velu-accordion__content">{children}</div>
50
- </details>
51
- );
52
- }
53
-
54
- export function AccordionGroup({ children, className = '', ...rest }) {
55
- return (
56
- <GroupCtx.Provider value={true}>
57
- <div className={`velu-accordion-group ${className}`.trim()} {...rest}>
58
- {children}
59
- </div>
60
- </GroupCtx.Provider>
61
- );
62
- }
63
-
64
- export default Accordion;
1
+ import React from 'react';
2
+ import resolveIcon from '../lib/resolveIcon.jsx';
3
+
4
+ /**
5
+ * Accordion + AccordionGroup — collapsible disclosure.
6
+ *
7
+ * Native <details>/<summary>: zero JS, SSR-safe, accessible, chevron rotates
8
+ * via CSS on [open] (same pattern as Sidebar). Fully tokenized — border /
9
+ * hover / expanded surface / radius / spacing / type all from base.css
10
+ * tokens, so light + dark theme automatically via [data-theme].
11
+ *
12
+ * - <Accordion title=… defaultOpen?>content</Accordion> — standalone bordered
13
+ * rounded box.
14
+ * - <AccordionGroup><Accordion/>…</AccordionGroup> — items joined into ONE
15
+ * bordered box with dividers between them (no per-item border/radius). A
16
+ * context tells nested Accordions they're grouped so they drop their own
17
+ * chrome; the group container owns the border + radius + dividers.
18
+ *
19
+ * @param {{ title: React.ReactNode, defaultOpen?: boolean,
20
+ * children: React.ReactNode, className?: string }} props
21
+ */
22
+
23
+ const GroupCtx = React.createContext(false);
24
+
25
+ export function Accordion({
26
+ title,
27
+ defaultOpen = false,
28
+ children,
29
+ className = '',
30
+ ...rest
31
+ }) {
32
+ const inGroup = React.useContext(GroupCtx);
33
+ const cls = [
34
+ 'velu-accordion',
35
+ inGroup ? 'velu-accordion--in-group' : '',
36
+ className,
37
+ ]
38
+ .filter(Boolean)
39
+ .join(' ');
40
+
41
+ return (
42
+ <details className={cls} open={defaultOpen || undefined} {...rest}>
43
+ <summary className="velu-accordion__summary">
44
+ <span className="velu-accordion__chevron" aria-hidden="true">
45
+ {resolveIcon('chevron-right', { size: '1em' })}
46
+ </span>
47
+ <span className="velu-accordion__title">{title}</span>
48
+ </summary>
49
+ <div className="velu-accordion__content">{children}</div>
50
+ </details>
51
+ );
52
+ }
53
+
54
+ export function AccordionGroup({ children, className = '', ...rest }) {
55
+ return (
56
+ <GroupCtx.Provider value={true}>
57
+ <div className={`velu-accordion-group ${className}`.trim()} {...rest}>
58
+ {children}
59
+ </div>
60
+ </GroupCtx.Provider>
61
+ );
62
+ }
63
+
64
+ export default Accordion;
@@ -1,207 +1,207 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import resolveIcon from '../lib/resolveIcon.jsx';
3
- import Cluster from '../primitives/Cluster.jsx';
4
- import MethodBadge from './MethodBadge.jsx';
5
- import TryItBar from './TryItBar.jsx';
6
-
7
- /**
8
- * ApiClient — the "try-it" playground layout shell.
9
- *
10
- * <ApiClient
11
- * method="DELETE"
12
- * label="Trigger Delete"
13
- * path="/project/preview/{projectId}"
14
- * description="Trigger a documentation deployment…"
15
- * onClose={() => …}
16
- * aside={
17
- * <CodeGroup>…response / samples / examples…</CodeGroup>
18
- * }
19
- * >
20
- * <AccordionGroup>
21
- * <Accordion title="Authorization">
22
- * <ApiField name="Authorization" type="string" required
23
- * prefix="Bearer" />
24
- * </Accordion>
25
- * <Accordion title="Path">
26
- * <ApiField name="projectId" type="string" required />
27
- * </Accordion>
28
- * </AccordionGroup>
29
- * </ApiClient>
30
- *
31
- * ApiClient is a pure layout shell — it owns the frame, the close
32
- * button, the operation/endpoint head row, and the two-column body.
33
- * It does NOT re-implement collapsibles or code panels:
34
- *
35
- * - LEFT column = `children` — compose the request sections with the
36
- * shared <Accordion> / <AccordionGroup> + <ApiField>.
37
- * - RIGHT column = `aside` — compose the response / code-sample
38
- * panels with the shared <CodeBlock> / <CodeGroup>.
39
- *
40
- * CTAs (Send, the operation dropdown) are presentational stubs — wire
41
- * `onSend` / a real selector when the OpenAPI adapter lands.
42
- */
43
-
44
- export default function ApiClient({
45
- method = 'GET',
46
- label,
47
- path = '',
48
- description,
49
- operations = [],
50
- activeId,
51
- onSelectOperation,
52
- onSend,
53
- sending = false,
54
- onClose,
55
- response,
56
- aside,
57
- children,
58
- className = '',
59
- ...rest
60
- }) {
61
- const key = String(method).toLowerCase();
62
- const cls = `velu-api-client velu-api-client--${key} ${className}`.trim();
63
-
64
- // The operation dropdown is interactive only when there's more than one
65
- // operation to switch between and a handler to call.
66
- const canSwitch =
67
- Array.isArray(operations) && operations.length > 1 && typeof onSelectOperation === 'function';
68
- const [menuOpen, setMenuOpen] = useState(false);
69
- const opRef = useRef(null);
70
-
71
- useEffect(() => {
72
- if (!menuOpen) return undefined;
73
- const onDown = (e) => {
74
- if (opRef.current && !opRef.current.contains(e.target)) setMenuOpen(false);
75
- };
76
- const onKey = (e) => {
77
- if (e.key === 'Escape') setMenuOpen(false);
78
- };
79
- document.addEventListener('mousedown', onDown);
80
- document.addEventListener('keydown', onKey);
81
- return () => {
82
- document.removeEventListener('mousedown', onDown);
83
- document.removeEventListener('keydown', onKey);
84
- };
85
- }, [menuOpen]);
86
-
87
- return (
88
- <div className={cls} {...rest}>
89
- {/* Close button row — only when shown as a modal (onClose set). */}
90
- {onClose && (
91
- <div className="velu-api-client__close-row">
92
- <button
93
- type="button"
94
- className="velu-api-client__close"
95
- onClick={onClose}
96
- aria-label="Close"
97
- >
98
- {resolveIcon('x', { size: '1em' })}
99
- </button>
100
- </div>
101
- )}
102
-
103
- {/* Head — operation dropdown (dummy) + TryItBar relabeled "Send".
104
- Cluster keeps each at its natural width: the dropdown hugs its
105
- text (flex: none), the bar fills the rest (flex: 1). When the
106
- bar can't keep its min width, Cluster wraps it below the
107
- dropdown. */}
108
- <Cluster
109
- space="var(--s-1)"
110
- align="flex-start"
111
- className="velu-api-client__head"
112
- >
113
- <div className="velu-api-client__op" ref={opRef}>
114
- {canSwitch ? (
115
- <button
116
- type="button"
117
- className="velu-api-client__op-btn"
118
- onClick={() => setMenuOpen((v) => !v)}
119
- aria-haspopup="listbox"
120
- aria-expanded={menuOpen}
121
- >
122
- <MethodBadge method={method} />
123
- {label && (
124
- <span className="velu-api-client__op-label">{label}</span>
125
- )}
126
- <span className="velu-api-client__op-chevron" aria-hidden="true">
127
- {resolveIcon('chevron-down', { size: '1em' })}
128
- </span>
129
- </button>
130
- ) : (
131
- <>
132
- <MethodBadge method={method} />
133
- {label && (
134
- <span className="velu-api-client__op-label">{label}</span>
135
- )}
136
- <span className="velu-api-client__op-chevron" aria-hidden="true">
137
- {resolveIcon('chevron-down', { size: '1em' })}
138
- </span>
139
- </>
140
- )}
141
- {canSwitch && menuOpen && (
142
- <ul
143
- className="velu-api-client__op-menu velu-hide-scrollbar"
144
- role="listbox"
145
- >
146
- {operations.map((o) => (
147
- <li key={o.id}>
148
- <button
149
- type="button"
150
- role="option"
151
- aria-selected={o.id === activeId}
152
- className={`velu-api-client__op-item${
153
- o.id === activeId ? ' velu-api-client__op-item--active' : ''
154
- }`}
155
- onClick={() => {
156
- onSelectOperation(o.id);
157
- setMenuOpen(false);
158
- }}
159
- >
160
- <MethodBadge method={o.method} />
161
- <span className="velu-api-client__op-item-label">
162
- {o.title}
163
- </span>
164
- </button>
165
- </li>
166
- ))}
167
- </ul>
168
- )}
169
- </div>
170
- <TryItBar
171
- method={method}
172
- path={path}
173
- cta="Send"
174
- onTry={onSend}
175
- loading={sending}
176
- className="velu-api-client__bar"
177
- />
178
- </Cluster>
179
-
180
- {/* Body grid (areas: form / response / aside). Desktop: form fills the
181
- left column, response + aside stack in the right. Mobile (single
182
- column): the live response moves ABOVE the form so a Send result is
183
- visible without scrolling past the inputs; the form and code samples
184
- follow. See api.css. */}
185
- <div
186
- className="velu-api-client__body"
187
- data-has-response={response != null ? 'true' : undefined}
188
- >
189
- <div className="velu-api-client__left">
190
- {label && (
191
- <h3 className="velu-api-client__title">{label}</h3>
192
- )}
193
- {description && (
194
- <p className="velu-api-client__description">{description}</p>
195
- )}
196
- {children}
197
- </div>
198
- {response != null && (
199
- <div className="velu-api-client__response">{response}</div>
200
- )}
201
- {aside != null && (
202
- <div className="velu-api-client__right">{aside}</div>
203
- )}
204
- </div>
205
- </div>
206
- );
207
- }
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import resolveIcon from '../lib/resolveIcon.jsx';
3
+ import Cluster from '../primitives/Cluster.jsx';
4
+ import MethodBadge from './MethodBadge.jsx';
5
+ import TryItBar from './TryItBar.jsx';
6
+
7
+ /**
8
+ * ApiClient — the "try-it" playground layout shell.
9
+ *
10
+ * <ApiClient
11
+ * method="DELETE"
12
+ * label="Trigger Delete"
13
+ * path="/project/preview/{projectId}"
14
+ * description="Trigger a documentation deployment…"
15
+ * onClose={() => …}
16
+ * aside={
17
+ * <CodeGroup>…response / samples / examples…</CodeGroup>
18
+ * }
19
+ * >
20
+ * <AccordionGroup>
21
+ * <Accordion title="Authorization">
22
+ * <ApiField name="Authorization" type="string" required
23
+ * prefix="Bearer" />
24
+ * </Accordion>
25
+ * <Accordion title="Path">
26
+ * <ApiField name="projectId" type="string" required />
27
+ * </Accordion>
28
+ * </AccordionGroup>
29
+ * </ApiClient>
30
+ *
31
+ * ApiClient is a pure layout shell — it owns the frame, the close
32
+ * button, the operation/endpoint head row, and the two-column body.
33
+ * It does NOT re-implement collapsibles or code panels:
34
+ *
35
+ * - LEFT column = `children` — compose the request sections with the
36
+ * shared <Accordion> / <AccordionGroup> + <ApiField>.
37
+ * - RIGHT column = `aside` — compose the response / code-sample
38
+ * panels with the shared <CodeBlock> / <CodeGroup>.
39
+ *
40
+ * CTAs (Send, the operation dropdown) are presentational stubs — wire
41
+ * `onSend` / a real selector when the OpenAPI adapter lands.
42
+ */
43
+
44
+ export default function ApiClient({
45
+ method = 'GET',
46
+ label,
47
+ path = '',
48
+ description,
49
+ operations = [],
50
+ activeId,
51
+ onSelectOperation,
52
+ onSend,
53
+ sending = false,
54
+ onClose,
55
+ response,
56
+ aside,
57
+ children,
58
+ className = '',
59
+ ...rest
60
+ }) {
61
+ const key = String(method).toLowerCase();
62
+ const cls = `velu-api-client velu-api-client--${key} ${className}`.trim();
63
+
64
+ // The operation dropdown is interactive only when there's more than one
65
+ // operation to switch between and a handler to call.
66
+ const canSwitch =
67
+ Array.isArray(operations) && operations.length > 1 && typeof onSelectOperation === 'function';
68
+ const [menuOpen, setMenuOpen] = useState(false);
69
+ const opRef = useRef(null);
70
+
71
+ useEffect(() => {
72
+ if (!menuOpen) return undefined;
73
+ const onDown = (e) => {
74
+ if (opRef.current && !opRef.current.contains(e.target)) setMenuOpen(false);
75
+ };
76
+ const onKey = (e) => {
77
+ if (e.key === 'Escape') setMenuOpen(false);
78
+ };
79
+ document.addEventListener('mousedown', onDown);
80
+ document.addEventListener('keydown', onKey);
81
+ return () => {
82
+ document.removeEventListener('mousedown', onDown);
83
+ document.removeEventListener('keydown', onKey);
84
+ };
85
+ }, [menuOpen]);
86
+
87
+ return (
88
+ <div className={cls} {...rest}>
89
+ {/* Close button row — only when shown as a modal (onClose set). */}
90
+ {onClose && (
91
+ <div className="velu-api-client__close-row">
92
+ <button
93
+ type="button"
94
+ className="velu-api-client__close"
95
+ onClick={onClose}
96
+ aria-label="Close"
97
+ >
98
+ {resolveIcon('x', { size: '1em' })}
99
+ </button>
100
+ </div>
101
+ )}
102
+
103
+ {/* Head — operation dropdown (dummy) + TryItBar relabeled "Send".
104
+ Cluster keeps each at its natural width: the dropdown hugs its
105
+ text (flex: none), the bar fills the rest (flex: 1). When the
106
+ bar can't keep its min width, Cluster wraps it below the
107
+ dropdown. */}
108
+ <Cluster
109
+ space="var(--s-1)"
110
+ align="flex-start"
111
+ className="velu-api-client__head"
112
+ >
113
+ <div className="velu-api-client__op" ref={opRef}>
114
+ {canSwitch ? (
115
+ <button
116
+ type="button"
117
+ className="velu-api-client__op-btn"
118
+ onClick={() => setMenuOpen((v) => !v)}
119
+ aria-haspopup="listbox"
120
+ aria-expanded={menuOpen}
121
+ >
122
+ <MethodBadge method={method} />
123
+ {label && (
124
+ <span className="velu-api-client__op-label">{label}</span>
125
+ )}
126
+ <span className="velu-api-client__op-chevron" aria-hidden="true">
127
+ {resolveIcon('chevron-down', { size: '1em' })}
128
+ </span>
129
+ </button>
130
+ ) : (
131
+ <>
132
+ <MethodBadge method={method} />
133
+ {label && (
134
+ <span className="velu-api-client__op-label">{label}</span>
135
+ )}
136
+ <span className="velu-api-client__op-chevron" aria-hidden="true">
137
+ {resolveIcon('chevron-down', { size: '1em' })}
138
+ </span>
139
+ </>
140
+ )}
141
+ {canSwitch && menuOpen && (
142
+ <ul
143
+ className="velu-api-client__op-menu velu-hide-scrollbar"
144
+ role="listbox"
145
+ >
146
+ {operations.map((o) => (
147
+ <li key={o.id}>
148
+ <button
149
+ type="button"
150
+ role="option"
151
+ aria-selected={o.id === activeId}
152
+ className={`velu-api-client__op-item${
153
+ o.id === activeId ? ' velu-api-client__op-item--active' : ''
154
+ }`}
155
+ onClick={() => {
156
+ onSelectOperation(o.id);
157
+ setMenuOpen(false);
158
+ }}
159
+ >
160
+ <MethodBadge method={o.method} />
161
+ <span className="velu-api-client__op-item-label">
162
+ {o.title}
163
+ </span>
164
+ </button>
165
+ </li>
166
+ ))}
167
+ </ul>
168
+ )}
169
+ </div>
170
+ <TryItBar
171
+ method={method}
172
+ path={path}
173
+ cta="Send"
174
+ onTry={onSend}
175
+ loading={sending}
176
+ className="velu-api-client__bar"
177
+ />
178
+ </Cluster>
179
+
180
+ {/* Body grid (areas: form / response / aside). Desktop: form fills the
181
+ left column, response + aside stack in the right. Mobile (single
182
+ column): the live response moves ABOVE the form so a Send result is
183
+ visible without scrolling past the inputs; the form and code samples
184
+ follow. See api.css. */}
185
+ <div
186
+ className="velu-api-client__body"
187
+ data-has-response={response != null ? 'true' : undefined}
188
+ >
189
+ <div className="velu-api-client__left">
190
+ {label && (
191
+ <h3 className="velu-api-client__title">{label}</h3>
192
+ )}
193
+ {description && (
194
+ <p className="velu-api-client__description">{description}</p>
195
+ )}
196
+ {children}
197
+ </div>
198
+ {response != null && (
199
+ <div className="velu-api-client__response">{response}</div>
200
+ )}
201
+ {aside != null && (
202
+ <div className="velu-api-client__right">{aside}</div>
203
+ )}
204
+ </div>
205
+ </div>
206
+ );
207
+ }