@veluai/velu 0.2.25 → 0.2.27

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 (114) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +44 -44
  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 +125 -125
  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/ChangelogFilters.jsx +75 -75
  16. package/runtime/velu-ui/components/Chatbot.jsx +972 -972
  17. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  18. package/runtime/velu-ui/components/Columns.jsx +56 -56
  19. package/runtime/velu-ui/components/ContextMenu.jsx +327 -327
  20. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  21. package/runtime/velu-ui/components/Field.jsx +81 -81
  22. package/runtime/velu-ui/components/Image.jsx +163 -163
  23. package/runtime/velu-ui/components/Logo.jsx +31 -31
  24. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  25. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  26. package/runtime/velu-ui/components/NotFound.jsx +63 -63
  27. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  28. package/runtime/velu-ui/components/PageFooter.jsx +145 -145
  29. package/runtime/velu-ui/components/PageHeader.jsx +422 -422
  30. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  31. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  32. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  33. package/runtime/velu-ui/components/Search.jsx +460 -460
  34. package/runtime/velu-ui/components/Sidebar.jsx +246 -246
  35. package/runtime/velu-ui/components/SocialLinks.jsx +90 -90
  36. package/runtime/velu-ui/components/Steps.jsx +65 -65
  37. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  38. package/runtime/velu-ui/components/Toc.jsx +537 -537
  39. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  40. package/runtime/velu-ui/components/Tree.jsx +87 -87
  41. package/runtime/velu-ui/components/TryItBar.jsx +102 -102
  42. package/runtime/velu-ui/components/Update.jsx +92 -92
  43. package/runtime/velu-ui/components/accordion.css +92 -92
  44. package/runtime/velu-ui/components/api-page.css +208 -208
  45. package/runtime/velu-ui/components/api.css +635 -635
  46. package/runtime/velu-ui/components/ask-bar.css +103 -103
  47. package/runtime/velu-ui/components/card.css +105 -105
  48. package/runtime/velu-ui/components/changelog-filters.css +43 -43
  49. package/runtime/velu-ui/components/chatbot.css +687 -687
  50. package/runtime/velu-ui/components/code-block.css +263 -263
  51. package/runtime/velu-ui/components/context-menu.css +199 -199
  52. package/runtime/velu-ui/components/docs-layout.css +822 -822
  53. package/runtime/velu-ui/components/field.css +82 -82
  54. package/runtime/velu-ui/components/image.css +237 -237
  55. package/runtime/velu-ui/components/nav-select.css +157 -157
  56. package/runtime/velu-ui/components/not-found.css +94 -94
  57. package/runtime/velu-ui/components/page-feedback.css +261 -261
  58. package/runtime/velu-ui/components/page-footer.css +130 -130
  59. package/runtime/velu-ui/components/page-header.css +558 -558
  60. package/runtime/velu-ui/components/page-nav.css +50 -50
  61. package/runtime/velu-ui/components/powered-by.css +92 -92
  62. package/runtime/velu-ui/components/prompt.css +99 -99
  63. package/runtime/velu-ui/components/search.css +307 -307
  64. package/runtime/velu-ui/components/sidebar.css +205 -205
  65. package/runtime/velu-ui/components/steps.css +77 -77
  66. package/runtime/velu-ui/components/theme-toggle.css +102 -102
  67. package/runtime/velu-ui/components/toc-bar.css +234 -234
  68. package/runtime/velu-ui/components/tree.css +49 -49
  69. package/runtime/velu-ui/components/update.css +111 -111
  70. package/runtime/velu-ui/index.js +58 -58
  71. package/runtime/velu-ui/lib/api-send.js +118 -118
  72. package/runtime/velu-ui/lib/brand-icons.jsx +103 -103
  73. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  74. package/runtime/velu-ui/lib/copyText.js +64 -64
  75. package/runtime/velu-ui/lib/docs-assistant.js +273 -273
  76. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  77. package/runtime/velu-ui/lib/page-feedback.js +72 -72
  78. package/runtime/velu-ui/lib/pagefind.js +116 -116
  79. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  80. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  81. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  82. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  83. package/runtime/velu-ui/mdx-components.jsx +107 -107
  84. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  85. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  86. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  87. package/runtime/velu-ui/primitives/stack.css +3 -3
  88. package/runtime/velu-ui/primitives/switcher.css +25 -25
  89. package/runtime/velu-ui/styles.css +48 -48
  90. package/runtime/velu-ui/tokens.css +4 -4
  91. package/schema/velu.schema.json +419 -419
  92. package/src/lib/extract-mdx-error.js +170 -170
  93. package/src/lib/issues.js +159 -159
  94. package/src/lib/known-components.js +35 -35
  95. package/src/navigation.js +459 -459
  96. package/src/runtime/App.jsx +1754 -1754
  97. package/src/runtime/ErrorBoundary.jsx +54 -54
  98. package/src/runtime/client-entry.jsx +27 -27
  99. package/src/runtime/server-entry.jsx +24 -24
  100. package/src/template.html +48 -48
  101. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  102. package/templates/starter/ai-tools/cursor.mdx +17 -17
  103. package/templates/starter/api-reference/introduction.mdx +43 -43
  104. package/templates/starter/development.mdx +19 -19
  105. package/templates/starter/essentials/code.mdx +29 -29
  106. package/templates/starter/essentials/images.mdx +29 -29
  107. package/templates/starter/essentials/markdown.mdx +25 -25
  108. package/templates/starter/essentials/navigation.mdx +39 -39
  109. package/templates/starter/essentials/settings.mdx +30 -30
  110. package/templates/starter/favicon.svg +6 -6
  111. package/templates/starter/index.mdx +31 -31
  112. package/templates/starter/openapi.json +160 -160
  113. package/templates/starter/quickstart.mdx +31 -31
  114. package/templates/starter/velu.json +40 -40
@@ -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
+ }