@veluai/velu 0.1.14 → 0.1.16
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 +44 -23
- package/package.json +2 -1
- package/runtime/velu-ui/components/ApiClient.jsx +84 -8
- package/runtime/velu-ui/components/ApiReferencePage.jsx +388 -0
- package/runtime/velu-ui/components/ApiSamples.jsx +36 -0
- package/runtime/velu-ui/components/Logo.jsx +31 -0
- package/runtime/velu-ui/components/PageFooter.jsx +6 -74
- package/runtime/velu-ui/components/PageHeader.jsx +10 -2
- package/runtime/velu-ui/components/Sidebar.jsx +10 -2
- package/runtime/velu-ui/components/SocialLinks.jsx +90 -0
- package/runtime/velu-ui/components/TryItBar.jsx +15 -3
- package/runtime/velu-ui/components/api-page.css +215 -0
- package/runtime/velu-ui/components/api.css +117 -1
- package/runtime/velu-ui/components/page-footer.css +5 -5
- package/runtime/velu-ui/components/page-header.css +37 -0
- package/runtime/velu-ui/components/powered-by.css +20 -0
- package/runtime/velu-ui/index.js +4 -0
- package/runtime/velu-ui/lib/api-send.js +92 -0
- package/runtime/velu-ui/styles.css +1 -0
- package/schema/velu.schema.json +160 -0
- package/src/navigation.js +11 -2
- package/src/runtime/App.jsx +109 -66
- package/templates/starter/api-reference/introduction.mdx +29 -14
- package/templates/starter/openapi.json +160 -0
- package/templates/starter/velu.json +6 -2
- package/templates/starter/api-reference/endpoint/create.mdx +0 -24
- package/templates/starter/api-reference/endpoint/get.mdx +0 -27
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Cluster from '../primitives/Cluster.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SocialLinks — a row of social-platform icon links. Shared by the full
|
|
6
|
+
* PageFooter (centered at its base) and the compact content-foot row (next to
|
|
7
|
+
* "Powered by Velu" when there are no footer link columns).
|
|
8
|
+
*
|
|
9
|
+
* `socials` is `[{ kind, href }]`. Unknown kinds (no icon) are skipped.
|
|
10
|
+
*
|
|
11
|
+
* @param {{ socials?: {kind: string, href: string}[],
|
|
12
|
+
* justify?: string, className?: string }} props
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/* Inline SVGs, fill: currentColor so they inherit the themed text color. */
|
|
16
|
+
function GithubIcon() {
|
|
17
|
+
return (
|
|
18
|
+
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
19
|
+
<path
|
|
20
|
+
fillRule="evenodd"
|
|
21
|
+
clipRule="evenodd"
|
|
22
|
+
d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"
|
|
23
|
+
/>
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function XIcon() {
|
|
28
|
+
return (
|
|
29
|
+
<svg viewBox="0 0 1200 1227" fill="currentColor" aria-hidden="true">
|
|
30
|
+
<path d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z" />
|
|
31
|
+
</svg>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function YoutubeIcon() {
|
|
35
|
+
return (
|
|
36
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
37
|
+
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
|
38
|
+
</svg>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function LinkedinIcon() {
|
|
42
|
+
return (
|
|
43
|
+
<svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">
|
|
44
|
+
<path d="M218.123 218.127h-37.931v-59.403c0-14.165-.253-32.4-19.728-32.4-19.756 0-22.779 15.434-22.779 31.369v60.43h-37.93V95.967h36.413v16.694h.51a39.907 39.907 0 0 1 35.928-19.733c38.445 0 45.533 25.288 45.533 58.186l-.016 67.013ZM56.955 79.27c-12.157.002-22.014-9.852-22.016-22.009-.002-12.157 9.851-22.014 22.008-22.016 12.157-.003 22.014 9.851 22.016 22.008A22.013 22.013 0 0 1 56.955 79.27m18.966 138.858H37.95V95.967h37.97v122.16ZM237.033.018H18.89C8.58-.098.125 8.161-.001 18.471v219.053c.122 10.315 8.576 18.582 18.89 18.474h218.144c10.336.128 18.823-8.139 18.966-18.474V18.454c-.147-10.33-8.635-18.588-18.966-18.453" />
|
|
45
|
+
</svg>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const SOCIAL_ICONS = {
|
|
50
|
+
github: GithubIcon,
|
|
51
|
+
x: XIcon,
|
|
52
|
+
youtube: YoutubeIcon,
|
|
53
|
+
linkedin: LinkedinIcon,
|
|
54
|
+
};
|
|
55
|
+
const SOCIAL_LABELS = {
|
|
56
|
+
github: 'GitHub',
|
|
57
|
+
x: 'X',
|
|
58
|
+
youtube: 'YouTube',
|
|
59
|
+
linkedin: 'LinkedIn',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default function SocialLinks({ socials = [], justify, className = '' }) {
|
|
63
|
+
const known = socials.filter((s) => s && SOCIAL_ICONS[s.kind]);
|
|
64
|
+
if (!known.length) return null;
|
|
65
|
+
return (
|
|
66
|
+
<Cluster
|
|
67
|
+
space="var(--s-2)"
|
|
68
|
+
align="center"
|
|
69
|
+
justify={justify}
|
|
70
|
+
className={`velu-socials ${className}`.trim()}
|
|
71
|
+
>
|
|
72
|
+
{known.map((s, i) => {
|
|
73
|
+
const Icon = SOCIAL_ICONS[s.kind];
|
|
74
|
+
return (
|
|
75
|
+
<a
|
|
76
|
+
key={i}
|
|
77
|
+
href={s.href}
|
|
78
|
+
target="_blank"
|
|
79
|
+
rel="noreferrer"
|
|
80
|
+
aria-label={SOCIAL_LABELS[s.kind] ?? s.kind}
|
|
81
|
+
className="velu-social"
|
|
82
|
+
data-kind={s.kind}
|
|
83
|
+
>
|
|
84
|
+
<Icon />
|
|
85
|
+
</a>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
</Cluster>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -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,215 @@
|
|
|
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
|
+
/* Playground right column: live response (when present) stacked above the
|
|
143
|
+
same request/response code samples shown in the page's right rail. */
|
|
144
|
+
.velu-api-pg-aside {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: var(--s2);
|
|
148
|
+
}
|
|
149
|
+
/* Breathing room inside each sample group — the code panels were reading
|
|
150
|
+
as cramped against the rail edge. */
|
|
151
|
+
.velu-api-samples > * {
|
|
152
|
+
margin: 0;
|
|
153
|
+
}
|
|
154
|
+
.velu-api-samples :is(pre, code) {
|
|
155
|
+
font-size: var(--f-h6);
|
|
156
|
+
line-height: 1.6;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ── Try-It playground modal ───────────────────────────────────────────── */
|
|
160
|
+
/* The playground renders above the page on a dimmed, blurred backdrop
|
|
161
|
+
rather than inline. */
|
|
162
|
+
.velu-api-modal {
|
|
163
|
+
position: fixed;
|
|
164
|
+
inset: 0;
|
|
165
|
+
z-index: 80;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: flex-start;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
padding: clamp(var(--s1), 6vh, var(--s5)) var(--s2);
|
|
170
|
+
overflow-y: auto;
|
|
171
|
+
background: color-mix(in srgb, var(--page-bg) 35%, rgba(0, 0, 0, 0.55));
|
|
172
|
+
backdrop-filter: blur(6px) saturate(1.1);
|
|
173
|
+
-webkit-backdrop-filter: blur(6px) saturate(1.1);
|
|
174
|
+
animation: velu-api-modal-in 140ms ease-out;
|
|
175
|
+
}
|
|
176
|
+
.velu-api-modal__panel {
|
|
177
|
+
inline-size: 100%;
|
|
178
|
+
max-inline-size: 64rem;
|
|
179
|
+
border-radius: var(--radius-md);
|
|
180
|
+
box-shadow:
|
|
181
|
+
0 24px 64px -16px rgba(0, 0, 0, 0.45),
|
|
182
|
+
0 8px 24px -12px rgba(0, 0, 0, 0.35);
|
|
183
|
+
animation: velu-api-modal-rise 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
184
|
+
}
|
|
185
|
+
@keyframes velu-api-modal-in {
|
|
186
|
+
from { opacity: 0; }
|
|
187
|
+
to { opacity: 1; }
|
|
188
|
+
}
|
|
189
|
+
@keyframes velu-api-modal-rise {
|
|
190
|
+
from { opacity: 0; transform: translateY(8px) scale(0.99); }
|
|
191
|
+
to { opacity: 1; transform: none; }
|
|
192
|
+
}
|
|
193
|
+
@media (prefers-reduced-motion: reduce) {
|
|
194
|
+
.velu-api-modal,
|
|
195
|
+
.velu-api-modal__panel {
|
|
196
|
+
animation: none;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Compact method pill in the sidebar (replaces the icon for API endpoints). */
|
|
201
|
+
.velu-sidebar__method {
|
|
202
|
+
flex: none;
|
|
203
|
+
font-family: var(--font-mono);
|
|
204
|
+
font-size: 0.6rem;
|
|
205
|
+
font-weight: var(--weight-bold);
|
|
206
|
+
letter-spacing: 0.03em;
|
|
207
|
+
padding: 0.18em 0.36em;
|
|
208
|
+
border-radius: var(--radius-xs, 4px);
|
|
209
|
+
text-transform: uppercase;
|
|
210
|
+
}
|
|
211
|
+
.velu-sidebar__method.velu-method-badge--get { color: var(--get-pill-color); background: color-mix(in srgb, var(--get-pill-color) 14%, transparent); }
|
|
212
|
+
.velu-sidebar__method.velu-method-badge--post { color: var(--post-pill-color); background: color-mix(in srgb, var(--post-pill-color) 14%, transparent); }
|
|
213
|
+
.velu-sidebar__method.velu-method-badge--put { color: var(--put-pill-color); background: color-mix(in srgb, var(--put-pill-color) 14%, transparent); }
|
|
214
|
+
.velu-sidebar__method.velu-method-badge--patch { color: var(--patch-pill-color); background: color-mix(in srgb, var(--patch-pill-color) 14%, transparent); }
|
|
215
|
+
.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); }
|
|
@@ -262,6 +314,7 @@
|
|
|
262
314
|
AND natural height; the Cluster's align: flex-start keeps it from
|
|
263
315
|
stretching to the taller TryItBar's height). */
|
|
264
316
|
.velu-api-client__op {
|
|
317
|
+
position: relative;
|
|
265
318
|
display: flex;
|
|
266
319
|
align-items: center;
|
|
267
320
|
gap: var(--s-2);
|
|
@@ -274,6 +327,20 @@
|
|
|
274
327
|
line-height: var(--lh-h6);
|
|
275
328
|
flex: none;
|
|
276
329
|
}
|
|
330
|
+
/* The clickable trigger when the operation is switchable — a bare button
|
|
331
|
+
that keeps the same layout as the static row. */
|
|
332
|
+
.velu-api-client__op-btn {
|
|
333
|
+
display: inline-flex;
|
|
334
|
+
align-items: center;
|
|
335
|
+
gap: var(--s-2);
|
|
336
|
+
margin: 0;
|
|
337
|
+
padding: 0;
|
|
338
|
+
background: transparent;
|
|
339
|
+
border: 0;
|
|
340
|
+
color: inherit;
|
|
341
|
+
font: inherit;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
}
|
|
277
344
|
.velu-api-client__op-label {
|
|
278
345
|
font-weight: var(--weight-medium);
|
|
279
346
|
}
|
|
@@ -281,6 +348,55 @@
|
|
|
281
348
|
display: inline-flex;
|
|
282
349
|
align-items: center;
|
|
283
350
|
color: var(--muted-color);
|
|
351
|
+
transition: transform 0.15s ease;
|
|
352
|
+
}
|
|
353
|
+
.velu-api-client__op-btn[aria-expanded='true'] .velu-api-client__op-chevron {
|
|
354
|
+
transform: rotate(180deg);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Operation switcher menu — lists every operation in the tab. */
|
|
358
|
+
.velu-api-client__op-menu {
|
|
359
|
+
position: absolute;
|
|
360
|
+
inset-block-start: calc(100% + var(--s-3));
|
|
361
|
+
inset-inline-start: 0;
|
|
362
|
+
z-index: 5;
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
gap: 2px;
|
|
366
|
+
margin: 0;
|
|
367
|
+
padding: var(--s-3);
|
|
368
|
+
list-style: none;
|
|
369
|
+
min-inline-size: 16rem;
|
|
370
|
+
max-block-size: 18rem;
|
|
371
|
+
overflow-y: auto;
|
|
372
|
+
background: var(--page-bg);
|
|
373
|
+
border: var(--border-width) solid var(--border-color);
|
|
374
|
+
border-radius: var(--radius-md);
|
|
375
|
+
box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.4);
|
|
376
|
+
}
|
|
377
|
+
.velu-api-client__op-item {
|
|
378
|
+
display: flex;
|
|
379
|
+
align-items: center;
|
|
380
|
+
gap: var(--s-2);
|
|
381
|
+
inline-size: 100%;
|
|
382
|
+
padding: var(--s-3) var(--s-2);
|
|
383
|
+
background: transparent;
|
|
384
|
+
border: 0;
|
|
385
|
+
border-radius: var(--radius-sm);
|
|
386
|
+
color: var(--text-color);
|
|
387
|
+
font: inherit;
|
|
388
|
+
font-size: var(--f-h6);
|
|
389
|
+
text-align: start;
|
|
390
|
+
cursor: pointer;
|
|
391
|
+
}
|
|
392
|
+
.velu-api-client__op-item:hover,
|
|
393
|
+
.velu-api-client__op-item--active {
|
|
394
|
+
background: var(--surface-color);
|
|
395
|
+
}
|
|
396
|
+
.velu-api-client__op-item-label {
|
|
397
|
+
white-space: nowrap;
|
|
398
|
+
overflow: hidden;
|
|
399
|
+
text-overflow: ellipsis;
|
|
284
400
|
}
|
|
285
401
|
|
|
286
402
|
/* Inside ApiClient the outer frame already supplies the box — strip
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
little breathing room remains around it. Each kind gets its OWN
|
|
101
101
|
brand color (set via the data-kind attribute below). Monochrome
|
|
102
102
|
brands (github, x) inherit --text-color so they theme correctly. */
|
|
103
|
-
.velu-
|
|
103
|
+
.velu-social {
|
|
104
104
|
display: inline-flex;
|
|
105
105
|
align-items: center;
|
|
106
106
|
justify-content: center;
|
|
@@ -112,19 +112,19 @@
|
|
|
112
112
|
text-decoration: none;
|
|
113
113
|
transition: opacity 0.12s ease;
|
|
114
114
|
}
|
|
115
|
-
.velu-
|
|
115
|
+
.velu-social:hover {
|
|
116
116
|
opacity: 0.78;
|
|
117
117
|
}
|
|
118
|
-
.velu-
|
|
118
|
+
.velu-social svg {
|
|
119
119
|
inline-size: var(--icon-size-lg);
|
|
120
120
|
block-size: var(--icon-size-lg);
|
|
121
121
|
}
|
|
122
122
|
/* Brand colors — YouTube red, LinkedIn blue. GitHub + X stay
|
|
123
123
|
--text-color (their canonical brand is monochrome and follows the
|
|
124
124
|
surrounding theme). */
|
|
125
|
-
.velu-
|
|
125
|
+
.velu-social[data-kind='youtube'] {
|
|
126
126
|
color: #ff0000;
|
|
127
127
|
}
|
|
128
|
-
.velu-
|
|
128
|
+
.velu-social[data-kind='linkedin'] {
|
|
129
129
|
color: #0a66c2;
|
|
130
130
|
}
|
|
@@ -100,6 +100,28 @@
|
|
|
100
100
|
color: var(--text-color);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/* Configured site logo (replaces the mark + wordmark). Height-constrained to
|
|
104
|
+
the brand row; width scales by aspect ratio up to a sane cap. */
|
|
105
|
+
.velu-logo {
|
|
106
|
+
display: block;
|
|
107
|
+
block-size: 2rem;
|
|
108
|
+
inline-size: auto;
|
|
109
|
+
max-inline-size: 12rem;
|
|
110
|
+
object-fit: contain;
|
|
111
|
+
}
|
|
112
|
+
/* Per-theme swap: light image by default, dark image under [data-theme=dark].
|
|
113
|
+
`display` (not opacity) so the inactive one leaves the a11y tree too. The
|
|
114
|
+
`mono` single-image case has neither modifier and always shows. */
|
|
115
|
+
.velu-logo--dark {
|
|
116
|
+
display: none;
|
|
117
|
+
}
|
|
118
|
+
[data-theme='dark'] .velu-logo--light {
|
|
119
|
+
display: none;
|
|
120
|
+
}
|
|
121
|
+
[data-theme='dark'] .velu-logo--dark {
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
|
|
103
125
|
/* ── Action buttons ─────────────────────────────────────────────────── */
|
|
104
126
|
.velu-header__action {
|
|
105
127
|
display: inline-flex;
|
|
@@ -136,6 +158,21 @@
|
|
|
136
158
|
.velu-header__action--primary:hover {
|
|
137
159
|
background: color-mix(in srgb, var(--accent-color) 88%, #000);
|
|
138
160
|
}
|
|
161
|
+
/* Plain text link (navbar.links) — no fill or border, just text that goes
|
|
162
|
+
accent on hover. Transparent border keeps it the same height as the
|
|
163
|
+
buttons beside it; tighter inline padding reads as a link, not a button. */
|
|
164
|
+
.velu-header__action--link {
|
|
165
|
+
background: transparent;
|
|
166
|
+
border: var(--border-width) solid transparent;
|
|
167
|
+
color: var(--text-color);
|
|
168
|
+
padding-inline: var(--s-5);
|
|
169
|
+
}
|
|
170
|
+
.velu-header__action--link:hover {
|
|
171
|
+
color: var(--accent-color);
|
|
172
|
+
}
|
|
173
|
+
.velu-header__action--link:hover .velu-header__action-icon {
|
|
174
|
+
color: var(--accent-color);
|
|
175
|
+
}
|
|
139
176
|
.velu-header__action-icon {
|
|
140
177
|
display: inline-flex;
|
|
141
178
|
flex: none;
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
/* PoweredBy — "Powered by Velu" badge, right-aligned. Sits above the
|
|
2
2
|
site footer. All values are tokens; light/dark via [data-theme]. */
|
|
3
3
|
|
|
4
|
+
/* Content foot — social icons (left) + the "Powered by" badge (right) in one
|
|
5
|
+
row. Shown at the base of the article column when the footer has no link
|
|
6
|
+
columns (so there's no full footer section). The row owns the vertical
|
|
7
|
+
rhythm; the nested badge drops its own standalone margins. */
|
|
8
|
+
.velu-content-foot {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
gap: var(--s2);
|
|
13
|
+
margin-block: 2rem;
|
|
14
|
+
padding-inline: var(--s3);
|
|
15
|
+
}
|
|
16
|
+
.velu-content-foot .velu-powered-by {
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
}
|
|
20
|
+
.velu-content-foot .velu-social {
|
|
21
|
+
color: var(--muted-color);
|
|
22
|
+
}
|
|
23
|
+
|
|
4
24
|
.velu-powered-by {
|
|
5
25
|
display: flex;
|
|
6
26
|
justify-content: flex-end;
|
package/runtime/velu-ui/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export { default as Toc } from './components/Toc.jsx';
|
|
|
8
8
|
export { default as TocBar } from './components/TocBar.jsx';
|
|
9
9
|
export { default as Callout } from './components/Callout.jsx';
|
|
10
10
|
export { default as ErrorCard } from './components/ErrorCard.jsx';
|
|
11
|
+
export { default as Logo } from './components/Logo.jsx';
|
|
12
|
+
export { default as SocialLinks } from './components/SocialLinks.jsx';
|
|
11
13
|
export {
|
|
12
14
|
default as Accordion,
|
|
13
15
|
AccordionGroup,
|
|
@@ -24,6 +26,8 @@ export { default as TryItBar } from './components/TryItBar.jsx';
|
|
|
24
26
|
export { default as ApiField } from './components/ApiField.jsx';
|
|
25
27
|
export { default as ApiClient } from './components/ApiClient.jsx';
|
|
26
28
|
export { default as ApiSidebar } from './components/ApiSidebar.jsx';
|
|
29
|
+
export { default as ApiReferencePage } from './components/ApiReferencePage.jsx';
|
|
30
|
+
export { default as ApiSamples } from './components/ApiSamples.jsx';
|
|
27
31
|
export { default as AskBar } from './components/AskBar.jsx';
|
|
28
32
|
export { default as Chatbot } from './components/Chatbot.jsx';
|
|
29
33
|
export { default as PageFeedback } from './components/PageFeedback.jsx';
|