@veluai/velu 0.1.15 → 0.2.0
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/dist/cli.js +50 -23
- package/package.json +9 -3
- package/runtime/velu-ui/components/ApiClient.jsx +97 -11
- package/runtime/velu-ui/components/ApiReferencePage.jsx +384 -0
- package/runtime/velu-ui/components/ApiSamples.jsx +36 -0
- package/runtime/velu-ui/components/ContextMenu.jsx +272 -0
- package/runtime/velu-ui/components/NotFound.jsx +63 -0
- package/runtime/velu-ui/components/Sidebar.jsx +10 -2
- package/runtime/velu-ui/components/TryItBar.jsx +15 -3
- package/runtime/velu-ui/components/api-page.css +208 -0
- package/runtime/velu-ui/components/api.css +165 -9
- package/runtime/velu-ui/components/context-menu.css +170 -0
- package/runtime/velu-ui/components/docs-layout.css +18 -0
- package/runtime/velu-ui/components/not-found.css +94 -0
- package/runtime/velu-ui/components/powered-by.css +6 -0
- package/runtime/velu-ui/index.js +4 -0
- package/runtime/velu-ui/lib/api-send.js +92 -0
- package/runtime/velu-ui/lib/brand-icons.jsx +102 -0
- package/runtime/velu-ui/styles.css +3 -0
- package/schema/velu.schema.json +121 -0
- package/src/navigation.js +11 -2
- package/src/runtime/App.jsx +95 -6
- package/templates/starter/api-reference/introduction.mdx +29 -14
- package/templates/starter/openapi.json +160 -0
- package/templates/starter/velu.json +10 -2
- package/templates/starter/api-reference/endpoint/create.mdx +0 -24
- package/templates/starter/api-reference/endpoint/get.mdx +0 -27
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* NotFound — the 404 page ("Direction 1 · Classic" from the design):
|
|
6
|
+
* a big accent-colored numeral, a short message, and a three-action row
|
|
7
|
+
* (Back to Home / Search the docs / Ask AI). Centered; the host layout
|
|
8
|
+
* supplies the header + footer. Pure tokens, so light/dark follow
|
|
9
|
+
* [data-theme] automatically.
|
|
10
|
+
*
|
|
11
|
+
* @param {{
|
|
12
|
+
* homeHref?: string,
|
|
13
|
+
* linkComponent?: React.ElementType,
|
|
14
|
+
* onSearch?: () => void,
|
|
15
|
+
* onAskAI?: () => void,
|
|
16
|
+
* eyebrow?: string, title?: string, body?: string,
|
|
17
|
+
* }} props
|
|
18
|
+
*/
|
|
19
|
+
export default function NotFound({
|
|
20
|
+
homeHref = '/',
|
|
21
|
+
linkComponent = 'a',
|
|
22
|
+
onSearch,
|
|
23
|
+
onAskAI,
|
|
24
|
+
eyebrow = 'Error 404',
|
|
25
|
+
title = 'This page wandered off',
|
|
26
|
+
body = 'The page you’re looking for doesn’t exist or may have moved. Pick up the trail below.',
|
|
27
|
+
}) {
|
|
28
|
+
const Link = linkComponent;
|
|
29
|
+
return (
|
|
30
|
+
<section className="velu-404">
|
|
31
|
+
<p className="velu-404__eyebrow">{eyebrow}</p>
|
|
32
|
+
<div className="velu-404__num" aria-hidden="true">
|
|
33
|
+
404
|
|
34
|
+
</div>
|
|
35
|
+
<h1 className="velu-404__title">{title}</h1>
|
|
36
|
+
<p className="velu-404__body">{body}</p>
|
|
37
|
+
<div className="velu-404__actions">
|
|
38
|
+
<Link className="velu-404__btn velu-404__btn--primary" href={homeHref}>
|
|
39
|
+
<span className="velu-404__btn-ic" aria-hidden="true">
|
|
40
|
+
{resolveIcon('house', { size: '1em' })}
|
|
41
|
+
</span>
|
|
42
|
+
Back to Home
|
|
43
|
+
</Link>
|
|
44
|
+
{onSearch && (
|
|
45
|
+
<button type="button" className="velu-404__btn" onClick={onSearch}>
|
|
46
|
+
<span className="velu-404__btn-ic" aria-hidden="true">
|
|
47
|
+
{resolveIcon('search', { size: '1em' })}
|
|
48
|
+
</span>
|
|
49
|
+
Search the docs
|
|
50
|
+
</button>
|
|
51
|
+
)}
|
|
52
|
+
{onAskAI && (
|
|
53
|
+
<button type="button" className="velu-404__btn" onClick={onAskAI}>
|
|
54
|
+
<span className="velu-404__btn-ic" aria-hidden="true">
|
|
55
|
+
{resolveIcon('sparkles', { size: '1em' })}
|
|
56
|
+
</span>
|
|
57
|
+
Ask AI
|
|
58
|
+
</button>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -87,7 +87,7 @@ function ExternalIcon() {
|
|
|
87
87
|
/** Recursive node. depth 0 = top-level; depth >= 1 = nested (subitem style). */
|
|
88
88
|
function Node({ item, depth }) {
|
|
89
89
|
const { activeHref, Link } = React.useContext(SidebarCtx);
|
|
90
|
-
const { label, href = '#', icon, external, items } = item;
|
|
90
|
+
const { label, href = '#', icon, external, items, method } = item;
|
|
91
91
|
const base = depth === 0 ? 'velu-sidebar__item' : 'velu-sidebar__subitem';
|
|
92
92
|
|
|
93
93
|
if (items && items.length) {
|
|
@@ -125,7 +125,15 @@ function Node({ item, depth }) {
|
|
|
125
125
|
aria-current={active ? 'page' : undefined}
|
|
126
126
|
{...linkProps}
|
|
127
127
|
>
|
|
128
|
-
|
|
128
|
+
{method ? (
|
|
129
|
+
<span
|
|
130
|
+
className={`velu-sidebar__method velu-method-badge--${String(method).toLowerCase()}`}
|
|
131
|
+
>
|
|
132
|
+
{method}
|
|
133
|
+
</span>
|
|
134
|
+
) : (
|
|
135
|
+
<Icon icon={icon} />
|
|
136
|
+
)}
|
|
129
137
|
<span className="velu-sidebar__label">{label}</span>
|
|
130
138
|
{external ? <ExternalIcon /> : null}
|
|
131
139
|
</LinkTag>
|
|
@@ -21,6 +21,7 @@ export default function TryItBar({
|
|
|
21
21
|
path = '',
|
|
22
22
|
cta = 'Try It',
|
|
23
23
|
onTry,
|
|
24
|
+
loading = false,
|
|
24
25
|
className = '',
|
|
25
26
|
...rest
|
|
26
27
|
}) {
|
|
@@ -79,10 +80,21 @@ export default function TryItBar({
|
|
|
79
80
|
type="button"
|
|
80
81
|
className="velu-try-it__cta"
|
|
81
82
|
onClick={onTry}
|
|
83
|
+
data-loading={loading ? 'true' : undefined}
|
|
84
|
+
disabled={loading || undefined}
|
|
85
|
+
aria-busy={loading || undefined}
|
|
82
86
|
>
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
{/* Two stacked layers swap with a vertical slide: the label slides
|
|
88
|
+
down and out the bottom while the spinner slides in from the top.
|
|
89
|
+
overflow:hidden on the button clips both off-screen states. */}
|
|
90
|
+
<span className="velu-try-it__cta-label">
|
|
91
|
+
{cta}
|
|
92
|
+
<span className="velu-try-it__cta-icon" aria-hidden="true">
|
|
93
|
+
{resolveIcon('chevron-right', { size: '1em' })}
|
|
94
|
+
</span>
|
|
95
|
+
</span>
|
|
96
|
+
<span className="velu-try-it__cta-spinner" aria-hidden="true">
|
|
97
|
+
{resolveIcon('loader-circle', { size: '1em' })}
|
|
86
98
|
</span>
|
|
87
99
|
</button>
|
|
88
100
|
</Cluster>
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/* Auto-generated OpenAPI reference page. Composes existing API components; this
|
|
2
|
+
only adds the page-level rhythm, the response tabs, and the playground's
|
|
3
|
+
live-response panel + body input. All tokens — light/dark via [data-theme]. */
|
|
4
|
+
|
|
5
|
+
.velu-api-page__title {
|
|
6
|
+
font-size: var(--f-h1);
|
|
7
|
+
font-weight: var(--weight-bold);
|
|
8
|
+
line-height: 1.1;
|
|
9
|
+
letter-spacing: -0.02em;
|
|
10
|
+
margin: 0 0 var(--s1);
|
|
11
|
+
}
|
|
12
|
+
.velu-api-page__lede {
|
|
13
|
+
color: var(--muted-color);
|
|
14
|
+
margin: 0 0 var(--s2);
|
|
15
|
+
}
|
|
16
|
+
.velu-api-page__tryit {
|
|
17
|
+
margin-block: var(--s2);
|
|
18
|
+
}
|
|
19
|
+
.velu-api-page__section {
|
|
20
|
+
margin-block-start: var(--s3);
|
|
21
|
+
}
|
|
22
|
+
.velu-api-page__section > h2 {
|
|
23
|
+
font-size: var(--f-h3);
|
|
24
|
+
font-weight: var(--weight-semibold);
|
|
25
|
+
margin: 0 0 var(--s1);
|
|
26
|
+
}
|
|
27
|
+
.velu-api-page__enum {
|
|
28
|
+
margin-block-start: var(--s-3);
|
|
29
|
+
color: var(--muted-color);
|
|
30
|
+
font-size: var(--f-h6);
|
|
31
|
+
}
|
|
32
|
+
.velu-api-page__enum code {
|
|
33
|
+
margin-inline-end: var(--s-4);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ── Response sections (doc body) ──────────────────────────────────────── */
|
|
37
|
+
/* One default-open accordion per status; the summary shows a status pill +
|
|
38
|
+
the content-type. */
|
|
39
|
+
.velu-api-resp__head {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: baseline;
|
|
42
|
+
gap: var(--s0);
|
|
43
|
+
}
|
|
44
|
+
/* Plain status code — no pill chrome, no per-status color, regular weight. */
|
|
45
|
+
.velu-api-resp__status {
|
|
46
|
+
font-family: var(--font-mono);
|
|
47
|
+
}
|
|
48
|
+
.velu-api-resp__ctype {
|
|
49
|
+
color: var(--muted-color);
|
|
50
|
+
font-family: var(--font-mono);
|
|
51
|
+
font-size: var(--f-h6);
|
|
52
|
+
}
|
|
53
|
+
.velu-api-resp__desc {
|
|
54
|
+
color: var(--muted-color);
|
|
55
|
+
margin: 0 0 var(--s1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ── Playground live-response panel (ApiClient aside) ──────────────────── */
|
|
59
|
+
/* A Body / Header tabbed card reusing the CodeGroup tab chrome
|
|
60
|
+
(.velu-code-block__*): Body shows a status-colored callout + the response
|
|
61
|
+
body; Header shows the response headers as a fixed-height scrollable table. */
|
|
62
|
+
.velu-api-resp-panel__content {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
gap: var(--s1);
|
|
66
|
+
padding: var(--s1);
|
|
67
|
+
background: var(--page-bg);
|
|
68
|
+
}
|
|
69
|
+
.velu-api-resp-panel__status {
|
|
70
|
+
/* The Callout owns its color; keep the weight regular. */
|
|
71
|
+
font-size: var(--f-h6);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Body + Header tabs — fixed height, vertically scrollable, so a long
|
|
75
|
+
response never makes the panel grow unbounded. */
|
|
76
|
+
.velu-api-resp-headers__scroll,
|
|
77
|
+
.velu-api-resp-body__scroll {
|
|
78
|
+
max-block-size: 15rem;
|
|
79
|
+
overflow-y: auto;
|
|
80
|
+
}
|
|
81
|
+
/* The CodeBlock owns its own border/radius; let it fill the scroll box. */
|
|
82
|
+
.velu-api-resp-body__scroll > .velu-code-block {
|
|
83
|
+
border: 0;
|
|
84
|
+
border-radius: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Response headers table — flush rows with hairline dividers. */
|
|
88
|
+
.velu-api-resp-headers {
|
|
89
|
+
inline-size: 100%;
|
|
90
|
+
border-collapse: collapse;
|
|
91
|
+
font-size: var(--f-h6);
|
|
92
|
+
}
|
|
93
|
+
.velu-api-resp-headers tr + tr td {
|
|
94
|
+
border-block-start: var(--border-width) solid var(--border-color);
|
|
95
|
+
}
|
|
96
|
+
.velu-api-resp-headers td {
|
|
97
|
+
padding: var(--s-2) var(--s-1);
|
|
98
|
+
vertical-align: top;
|
|
99
|
+
word-break: break-word;
|
|
100
|
+
}
|
|
101
|
+
.velu-api-resp-headers__key {
|
|
102
|
+
color: var(--muted-color);
|
|
103
|
+
font-family: var(--font-mono);
|
|
104
|
+
white-space: nowrap;
|
|
105
|
+
padding-inline-end: var(--s1) !important;
|
|
106
|
+
}
|
|
107
|
+
.velu-api-resp-headers__val {
|
|
108
|
+
font-family: var(--font-mono);
|
|
109
|
+
inline-size: 100%;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Editable JSON body in the playground. */
|
|
113
|
+
.velu-api-body-input {
|
|
114
|
+
inline-size: 100%;
|
|
115
|
+
resize: vertical;
|
|
116
|
+
padding: var(--s0);
|
|
117
|
+
border: var(--border-width) solid var(--border-color);
|
|
118
|
+
border-radius: var(--radius-sm);
|
|
119
|
+
background: var(--page-bg);
|
|
120
|
+
color: var(--text-color);
|
|
121
|
+
font-family: var(--font-mono);
|
|
122
|
+
font-size: var(--f-h6);
|
|
123
|
+
line-height: 1.5;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ── Right-rail samples ────────────────────────────────────────────────── */
|
|
127
|
+
/* A TOC needs only ~280px, but the request/response code samples were being
|
|
128
|
+
cropped in that width. On API pages widen the right rail — both the aside
|
|
129
|
+
width AND the centre column's right margin read this one var, so they stay
|
|
130
|
+
in sync. (At < 1024px the @container query hides the rail + zeroes the
|
|
131
|
+
margin, so this wider value never affects narrow layouts.) */
|
|
132
|
+
.velu-docs-layout[data-api='true'] {
|
|
133
|
+
--velu-aside-right-width: 28rem; /* 448px */
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.velu-api-samples {
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
gap: var(--s2);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Breathing room inside each sample group — the code panels were reading
|
|
143
|
+
as cramped against the rail edge. */
|
|
144
|
+
.velu-api-samples > * {
|
|
145
|
+
margin: 0;
|
|
146
|
+
}
|
|
147
|
+
.velu-api-samples :is(pre, code) {
|
|
148
|
+
font-size: var(--f-h6);
|
|
149
|
+
line-height: 1.6;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* ── Try-It playground modal ───────────────────────────────────────────── */
|
|
153
|
+
/* The playground renders above the page on a dimmed, blurred backdrop
|
|
154
|
+
rather than inline. */
|
|
155
|
+
.velu-api-modal {
|
|
156
|
+
position: fixed;
|
|
157
|
+
inset: 0;
|
|
158
|
+
z-index: 80;
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: flex-start;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
padding: clamp(var(--s1), 6vh, var(--s5)) var(--s2);
|
|
163
|
+
overflow-y: auto;
|
|
164
|
+
background: color-mix(in srgb, var(--page-bg) 35%, rgba(0, 0, 0, 0.55));
|
|
165
|
+
backdrop-filter: blur(6px) saturate(1.1);
|
|
166
|
+
-webkit-backdrop-filter: blur(6px) saturate(1.1);
|
|
167
|
+
animation: velu-api-modal-in 140ms ease-out;
|
|
168
|
+
}
|
|
169
|
+
.velu-api-modal__panel {
|
|
170
|
+
inline-size: 100%;
|
|
171
|
+
max-inline-size: 64rem;
|
|
172
|
+
border-radius: var(--radius-md);
|
|
173
|
+
box-shadow:
|
|
174
|
+
0 24px 64px -16px rgba(0, 0, 0, 0.45),
|
|
175
|
+
0 8px 24px -12px rgba(0, 0, 0, 0.35);
|
|
176
|
+
animation: velu-api-modal-rise 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
177
|
+
}
|
|
178
|
+
@keyframes velu-api-modal-in {
|
|
179
|
+
from { opacity: 0; }
|
|
180
|
+
to { opacity: 1; }
|
|
181
|
+
}
|
|
182
|
+
@keyframes velu-api-modal-rise {
|
|
183
|
+
from { opacity: 0; transform: translateY(8px) scale(0.99); }
|
|
184
|
+
to { opacity: 1; transform: none; }
|
|
185
|
+
}
|
|
186
|
+
@media (prefers-reduced-motion: reduce) {
|
|
187
|
+
.velu-api-modal,
|
|
188
|
+
.velu-api-modal__panel {
|
|
189
|
+
animation: none;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Compact method pill in the sidebar (replaces the icon for API endpoints). */
|
|
194
|
+
.velu-sidebar__method {
|
|
195
|
+
flex: none;
|
|
196
|
+
font-family: var(--font-mono);
|
|
197
|
+
font-size: 0.6rem;
|
|
198
|
+
font-weight: var(--weight-bold);
|
|
199
|
+
letter-spacing: 0.03em;
|
|
200
|
+
padding: 0.18em 0.36em;
|
|
201
|
+
border-radius: var(--radius-xs, 4px);
|
|
202
|
+
text-transform: uppercase;
|
|
203
|
+
}
|
|
204
|
+
.velu-sidebar__method.velu-method-badge--get { color: var(--get-pill-color); background: color-mix(in srgb, var(--get-pill-color) 14%, transparent); }
|
|
205
|
+
.velu-sidebar__method.velu-method-badge--post { color: var(--post-pill-color); background: color-mix(in srgb, var(--post-pill-color) 14%, transparent); }
|
|
206
|
+
.velu-sidebar__method.velu-method-badge--put { color: var(--put-pill-color); background: color-mix(in srgb, var(--put-pill-color) 14%, transparent); }
|
|
207
|
+
.velu-sidebar__method.velu-method-badge--patch { color: var(--patch-pill-color); background: color-mix(in srgb, var(--patch-pill-color) 14%, transparent); }
|
|
208
|
+
.velu-sidebar__method.velu-method-badge--delete { color: var(--delete-pill-color); background: color-mix(in srgb, var(--delete-pill-color) 14%, transparent); }
|
|
@@ -175,9 +175,11 @@
|
|
|
175
175
|
color: var(--text-color);
|
|
176
176
|
}
|
|
177
177
|
.velu-try-it__cta {
|
|
178
|
+
position: relative;
|
|
179
|
+
overflow: hidden;
|
|
178
180
|
display: inline-flex;
|
|
179
181
|
align-items: center;
|
|
180
|
-
|
|
182
|
+
justify-content: center;
|
|
181
183
|
padding-block: var(--s-3);
|
|
182
184
|
padding-inline: var(--s0);
|
|
183
185
|
background: var(--method-cta, var(--accent-color));
|
|
@@ -194,6 +196,56 @@
|
|
|
194
196
|
.velu-try-it__cta:hover {
|
|
195
197
|
background: color-mix(in srgb, var(--method-cta, var(--accent-color)) 88%, #000);
|
|
196
198
|
}
|
|
199
|
+
.velu-try-it__cta:disabled {
|
|
200
|
+
cursor: default;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Label (text + chevron) and the loading spinner are two stacked layers
|
|
204
|
+
that swap with a vertical slide: when loading, the label slides DOWN and
|
|
205
|
+
out the bottom while the spinner slides IN from the top. overflow:hidden
|
|
206
|
+
on the button clips the off-screen states. The label stays in normal flow
|
|
207
|
+
so it fixes the button's width; the spinner overlays it. */
|
|
208
|
+
.velu-try-it__cta-label {
|
|
209
|
+
display: inline-flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
gap: var(--s-2);
|
|
212
|
+
transition: transform 0.22s ease, opacity 0.18s ease;
|
|
213
|
+
}
|
|
214
|
+
.velu-try-it__cta-spinner {
|
|
215
|
+
position: absolute;
|
|
216
|
+
inset: 0;
|
|
217
|
+
display: inline-flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
justify-content: center;
|
|
220
|
+
transform: translateY(-110%);
|
|
221
|
+
opacity: 0;
|
|
222
|
+
transition: transform 0.22s ease, opacity 0.18s ease;
|
|
223
|
+
}
|
|
224
|
+
.velu-try-it__cta[data-loading='true'] .velu-try-it__cta-label {
|
|
225
|
+
transform: translateY(120%);
|
|
226
|
+
opacity: 0;
|
|
227
|
+
}
|
|
228
|
+
.velu-try-it__cta[data-loading='true'] .velu-try-it__cta-spinner {
|
|
229
|
+
transform: translateY(0);
|
|
230
|
+
opacity: 1;
|
|
231
|
+
}
|
|
232
|
+
.velu-try-it__cta-spinner svg {
|
|
233
|
+
animation: velu-cta-spin 0.7s linear infinite;
|
|
234
|
+
}
|
|
235
|
+
@keyframes velu-cta-spin {
|
|
236
|
+
to {
|
|
237
|
+
transform: rotate(360deg);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
@media (prefers-reduced-motion: reduce) {
|
|
241
|
+
.velu-try-it__cta-label,
|
|
242
|
+
.velu-try-it__cta-spinner {
|
|
243
|
+
transition-duration: 0.01ms;
|
|
244
|
+
}
|
|
245
|
+
.velu-try-it__cta-spinner svg {
|
|
246
|
+
animation-duration: 1.4s;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
197
249
|
.velu-try-it--get { --method-cta: var(--get-cta-color); }
|
|
198
250
|
.velu-try-it--post { --method-cta: var(--post-cta-color); }
|
|
199
251
|
.velu-try-it--put { --method-cta: var(--put-cta-color); }
|
|
@@ -213,6 +265,9 @@
|
|
|
213
265
|
overflow: hidden;
|
|
214
266
|
max-inline-size: 100%;
|
|
215
267
|
min-inline-size: 0;
|
|
268
|
+
/* Own query container so the body grid reflows to the client's width
|
|
269
|
+
(it lives in a modal, outside the docs layout container). */
|
|
270
|
+
container: api-client / inline-size;
|
|
216
271
|
}
|
|
217
272
|
|
|
218
273
|
/* Modal close button — sits in its own row at the top of the client,
|
|
@@ -262,6 +317,7 @@
|
|
|
262
317
|
AND natural height; the Cluster's align: flex-start keeps it from
|
|
263
318
|
stretching to the taller TryItBar's height). */
|
|
264
319
|
.velu-api-client__op {
|
|
320
|
+
position: relative;
|
|
265
321
|
display: flex;
|
|
266
322
|
align-items: center;
|
|
267
323
|
gap: var(--s-2);
|
|
@@ -274,6 +330,20 @@
|
|
|
274
330
|
line-height: var(--lh-h6);
|
|
275
331
|
flex: none;
|
|
276
332
|
}
|
|
333
|
+
/* The clickable trigger when the operation is switchable — a bare button
|
|
334
|
+
that keeps the same layout as the static row. */
|
|
335
|
+
.velu-api-client__op-btn {
|
|
336
|
+
display: inline-flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
gap: var(--s-2);
|
|
339
|
+
margin: 0;
|
|
340
|
+
padding: 0;
|
|
341
|
+
background: transparent;
|
|
342
|
+
border: 0;
|
|
343
|
+
color: inherit;
|
|
344
|
+
font: inherit;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
}
|
|
277
347
|
.velu-api-client__op-label {
|
|
278
348
|
font-weight: var(--weight-medium);
|
|
279
349
|
}
|
|
@@ -281,6 +351,55 @@
|
|
|
281
351
|
display: inline-flex;
|
|
282
352
|
align-items: center;
|
|
283
353
|
color: var(--muted-color);
|
|
354
|
+
transition: transform 0.15s ease;
|
|
355
|
+
}
|
|
356
|
+
.velu-api-client__op-btn[aria-expanded='true'] .velu-api-client__op-chevron {
|
|
357
|
+
transform: rotate(180deg);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* Operation switcher menu — lists every operation in the tab. */
|
|
361
|
+
.velu-api-client__op-menu {
|
|
362
|
+
position: absolute;
|
|
363
|
+
inset-block-start: calc(100% + var(--s-3));
|
|
364
|
+
inset-inline-start: 0;
|
|
365
|
+
z-index: 5;
|
|
366
|
+
display: flex;
|
|
367
|
+
flex-direction: column;
|
|
368
|
+
gap: 2px;
|
|
369
|
+
margin: 0;
|
|
370
|
+
padding: var(--s-3);
|
|
371
|
+
list-style: none;
|
|
372
|
+
min-inline-size: 16rem;
|
|
373
|
+
max-block-size: 18rem;
|
|
374
|
+
overflow-y: auto;
|
|
375
|
+
background: var(--page-bg);
|
|
376
|
+
border: var(--border-width) solid var(--border-color);
|
|
377
|
+
border-radius: var(--radius-md);
|
|
378
|
+
box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.4);
|
|
379
|
+
}
|
|
380
|
+
.velu-api-client__op-item {
|
|
381
|
+
display: flex;
|
|
382
|
+
align-items: center;
|
|
383
|
+
gap: var(--s-2);
|
|
384
|
+
inline-size: 100%;
|
|
385
|
+
padding: var(--s-3) var(--s-2);
|
|
386
|
+
background: transparent;
|
|
387
|
+
border: 0;
|
|
388
|
+
border-radius: var(--radius-sm);
|
|
389
|
+
color: var(--text-color);
|
|
390
|
+
font: inherit;
|
|
391
|
+
font-size: var(--f-h6);
|
|
392
|
+
text-align: start;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
}
|
|
395
|
+
.velu-api-client__op-item:hover,
|
|
396
|
+
.velu-api-client__op-item--active {
|
|
397
|
+
background: var(--surface-color);
|
|
398
|
+
}
|
|
399
|
+
.velu-api-client__op-item-label {
|
|
400
|
+
white-space: nowrap;
|
|
401
|
+
overflow: hidden;
|
|
402
|
+
text-overflow: ellipsis;
|
|
284
403
|
}
|
|
285
404
|
|
|
286
405
|
/* Inside ApiClient the outer frame already supplies the box — strip
|
|
@@ -297,25 +416,62 @@
|
|
|
297
416
|
background: transparent;
|
|
298
417
|
}
|
|
299
418
|
|
|
300
|
-
/*
|
|
301
|
-
|
|
302
|
-
|
|
419
|
+
/* Body grid with named areas so the three slots — form / live response /
|
|
420
|
+
code samples — can be placed AND reordered independently.
|
|
421
|
+
Wide (the client is its own query container, set on .velu-api-client):
|
|
422
|
+
form fills the left column; response sits top-right with the samples
|
|
423
|
+
below it. When there's no response yet, the samples take the whole right
|
|
424
|
+
column.
|
|
425
|
+
Narrow (single column): the live response moves ABOVE the form so a Send
|
|
426
|
+
result is visible without scrolling past the inputs; form then samples
|
|
427
|
+
follow. */
|
|
303
428
|
.velu-api-client__body {
|
|
304
|
-
display:
|
|
305
|
-
|
|
429
|
+
display: grid;
|
|
430
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
431
|
+
grid-template-areas: 'form aside';
|
|
432
|
+
align-items: start;
|
|
306
433
|
gap: var(--s2);
|
|
307
434
|
padding: var(--s2);
|
|
308
435
|
}
|
|
309
|
-
.velu-api-
|
|
436
|
+
.velu-api-client__body[data-has-response='true'] {
|
|
437
|
+
grid-template-areas:
|
|
438
|
+
'form response'
|
|
439
|
+
'form aside';
|
|
440
|
+
}
|
|
441
|
+
.velu-api-client__left {
|
|
442
|
+
grid-area: form;
|
|
443
|
+
}
|
|
444
|
+
.velu-api-client__response {
|
|
445
|
+
grid-area: response;
|
|
446
|
+
}
|
|
310
447
|
.velu-api-client__right {
|
|
311
|
-
|
|
312
|
-
|
|
448
|
+
grid-area: aside;
|
|
449
|
+
}
|
|
450
|
+
.velu-api-client__left,
|
|
451
|
+
.velu-api-client__right,
|
|
452
|
+
.velu-api-client__response {
|
|
313
453
|
display: flex;
|
|
314
454
|
flex-direction: column;
|
|
315
455
|
gap: var(--s1);
|
|
316
456
|
min-inline-size: 0;
|
|
317
457
|
}
|
|
318
458
|
|
|
459
|
+
@container api-client (max-width: 40rem) {
|
|
460
|
+
.velu-api-client__body,
|
|
461
|
+
.velu-api-client__body[data-has-response='true'] {
|
|
462
|
+
grid-template-columns: 1fr;
|
|
463
|
+
grid-template-areas:
|
|
464
|
+
'form'
|
|
465
|
+
'aside';
|
|
466
|
+
}
|
|
467
|
+
.velu-api-client__body[data-has-response='true'] {
|
|
468
|
+
grid-template-areas:
|
|
469
|
+
'response'
|
|
470
|
+
'form'
|
|
471
|
+
'aside';
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
319
475
|
.velu-api-client__title {
|
|
320
476
|
margin: 0;
|
|
321
477
|
font-size: var(--f-h3);
|