@aravindc26/velu 0.11.0 → 0.11.3
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/package.json +15 -6
- package/schema/velu.schema.json +1251 -115
- package/src/build.ts +1121 -304
- package/src/cli.ts +90 -26
- package/src/engine/_server.mjs +1684 -277
- package/src/engine/app/(docs)/[...slug]/layout.tsx +371 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +926 -0
- package/src/engine/app/api/proxy/route.ts +23 -0
- package/src/engine/app/copy-page.css +59 -1
- package/src/engine/app/global.css +3157 -3
- package/src/engine/app/layout.tsx +56 -1
- package/src/engine/app/llms-file/route.ts +87 -0
- package/src/engine/app/llms-full-file/route.ts +62 -0
- package/src/engine/app/md-file/[...slug]/route.ts +409 -0
- package/src/engine/app/page.tsx +45 -0
- package/src/engine/app/robots.txt/route.ts +63 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +169 -0
- package/src/engine/app/sitemap.xml/route.ts +82 -0
- package/src/engine/components/assistant.tsx +16 -5
- package/src/engine/components/changelog-filters.tsx +114 -0
- package/src/engine/components/code-group.tsx +383 -0
- package/src/engine/components/color.tsx +118 -0
- package/src/engine/components/expandable.tsx +77 -0
- package/src/engine/components/icon.tsx +136 -0
- package/src/engine/components/image-zoom-fallback.tsx +147 -0
- package/src/engine/components/image.tsx +111 -0
- package/src/engine/components/manual-api-playground.tsx +154 -0
- package/src/engine/components/mermaid.tsx +142 -0
- package/src/engine/components/openapi-toc-sync.tsx +59 -0
- package/src/engine/components/openapi.tsx +1682 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +27 -3
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +1 -6
- package/src/engine/components/search.tsx +4 -0
- package/src/engine/components/sidebar-links.tsx +13 -15
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/toc-examples.tsx +110 -0
- package/src/engine/components/view.tsx +344 -0
- package/src/engine/generated/redirects.ts +3 -0
- package/src/engine/lib/changelog.ts +246 -0
- package/src/engine/lib/layout.shared.ts +30 -2
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +481 -412
- package/src/engine/lib/navigation-normalize.ts +261 -54
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +107 -4
- package/src/engine/lib/velu.ts +368 -2
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- package/src/engine/public/icons/cursor-dark.svg +12 -0
- package/src/engine/public/icons/cursor-light.svg +12 -0
- package/src/engine/source.config.ts +98 -1
- package/src/engine/src/components/PageTitle.astro +16 -5
- package/src/engine/src/lib/velu.ts +11 -3
- package/src/navigation-normalize.ts +252 -54
- package/src/themes.ts +6 -6
- package/src/validate.ts +119 -6
- package/src/engine/app/(docs)/[[...slug]]/layout.tsx +0 -87
- package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -146
|
@@ -1,9 +1,657 @@
|
|
|
1
1
|
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
|
2
2
|
import type { MDXComponents } from 'mdx/types';
|
|
3
|
+
import { cloneElement, isValidElement, type CSSProperties, type ReactElement, type ReactNode } from 'react';
|
|
4
|
+
import { Accordion as FumaAccordion, Accordions as FumaAccordions } from 'fumadocs-ui/components/accordion';
|
|
5
|
+
import { Tab as FumaTab, Tabs as FumaTabs } from 'fumadocs-ui/components/tabs';
|
|
6
|
+
import { Step as FumaStep, Steps as FumaSteps } from 'fumadocs-ui/components/steps';
|
|
7
|
+
import { File as FumaFile, Files as FumaFiles, Folder as FumaFolder } from 'fumadocs-ui/components/files';
|
|
8
|
+
import { VeluIcon } from '@/components/icon';
|
|
9
|
+
import { VeluCodeGroup } from '@/components/code-group';
|
|
10
|
+
import { VeluColor, VeluColorItem, VeluColorRow } from '@/components/color';
|
|
11
|
+
import { VeluExpandable } from '@/components/expandable';
|
|
12
|
+
import { VeluImage } from '@/components/image';
|
|
13
|
+
import { VeluMermaid } from '@/components/mermaid';
|
|
14
|
+
import { VeluPrompt } from '@/components/prompt';
|
|
15
|
+
import { VeluSyncedTabs } from '@/components/synced-tabs';
|
|
16
|
+
import { VeluView } from '@/components/view';
|
|
17
|
+
import { VeluOpenAPI, VeluOpenAPISchema } from '@/components/openapi';
|
|
18
|
+
import { getApiConfig, getIconLibrary } from '@/lib/velu';
|
|
3
19
|
|
|
4
20
|
export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
|
21
|
+
const Card = defaultMdxComponents.Card as any;
|
|
22
|
+
const Cards = defaultMdxComponents.Cards as any;
|
|
23
|
+
const iconLibrary = getIconLibrary();
|
|
24
|
+
const apiConfig = getApiConfig();
|
|
25
|
+
const defaultProxyUrl = apiConfig.playgroundProxyEnabled ? '/api/proxy' : '';
|
|
26
|
+
const Callout = defaultMdxComponents.Callout as any;
|
|
27
|
+
const CalloutTitle = defaultMdxComponents.CalloutTitle as any;
|
|
28
|
+
const CalloutDescription = defaultMdxComponents.CalloutDescription as any;
|
|
29
|
+
const toTokenList = (value: unknown): string[] => {
|
|
30
|
+
if (Array.isArray(value)) return value.map(String).filter(Boolean);
|
|
31
|
+
if (value == null || value === false) return [];
|
|
32
|
+
return [String(value)];
|
|
33
|
+
};
|
|
34
|
+
const hintIconName = 'lightbulb';
|
|
35
|
+
const VeluTreeFolder = ({
|
|
36
|
+
name,
|
|
37
|
+
defaultOpen = false,
|
|
38
|
+
openable = true,
|
|
39
|
+
children,
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: any) => {
|
|
43
|
+
if (openable === false) {
|
|
44
|
+
return (
|
|
45
|
+
<div className={['velu-tree-folder', 'velu-tree-folder-static', className].filter(Boolean).join(' ')} {...props}>
|
|
46
|
+
<div className="velu-tree-folder-label">{name ?? 'folder'}</div>
|
|
47
|
+
{children ? <div className="velu-tree-children">{children}</div> : null}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<FumaFolder
|
|
54
|
+
name={name ?? 'folder'}
|
|
55
|
+
defaultOpen={Boolean(defaultOpen)}
|
|
56
|
+
className={['velu-tree-folder', className].filter(Boolean).join(' ')}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</FumaFolder>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
const VeluTreeFile = ({ name, icon, className, ...props }: any) => (
|
|
64
|
+
<FumaFile
|
|
65
|
+
name={name ?? 'file'}
|
|
66
|
+
icon={icon}
|
|
67
|
+
className={['velu-tree-file', className].filter(Boolean).join(' ')}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
const VeluTree = Object.assign(
|
|
72
|
+
({ children, className, ...props }: any) => (
|
|
73
|
+
<FumaFiles className={['velu-tree', className].filter(Boolean).join(' ')} {...props}>
|
|
74
|
+
{children}
|
|
75
|
+
</FumaFiles>
|
|
76
|
+
),
|
|
77
|
+
{
|
|
78
|
+
Folder: VeluTreeFolder,
|
|
79
|
+
File: VeluTreeFile,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
const VeluFilesFolder = ({ disabled, ...props }: any) => (
|
|
83
|
+
<VeluTreeFolder openable={disabled ? false : undefined} {...props} />
|
|
84
|
+
);
|
|
85
|
+
const VeluFiles = Object.assign(
|
|
86
|
+
({ children, className, ...props }: any) => (
|
|
87
|
+
<FumaFiles className={['velu-files', className].filter(Boolean).join(' ')} {...props}>
|
|
88
|
+
{children}
|
|
89
|
+
</FumaFiles>
|
|
90
|
+
),
|
|
91
|
+
{
|
|
92
|
+
Folder: VeluFilesFolder,
|
|
93
|
+
File: VeluTreeFile,
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
const VeluCards = ({ className, ...props }: any) => (
|
|
97
|
+
<Cards
|
|
98
|
+
{...props}
|
|
99
|
+
className={['velu-card-group', className].filter(Boolean).join(' ')}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
|
|
5
103
|
return {
|
|
6
104
|
...defaultMdxComponents,
|
|
7
105
|
...components,
|
|
106
|
+
Cards: VeluCards as any,
|
|
107
|
+
img: VeluImage as any,
|
|
108
|
+
// Mint-style aliases used in imported docs content.
|
|
109
|
+
Note: ({ children }: { children?: ReactNode }) => (
|
|
110
|
+
<Callout type="info" className="velu-callout velu-callout-info">{children}</Callout>
|
|
111
|
+
),
|
|
112
|
+
Warning: ({ children }: { children?: ReactNode }) => (
|
|
113
|
+
<Callout type="warning" className="velu-callout velu-callout-warning">{children}</Callout>
|
|
114
|
+
),
|
|
115
|
+
Info: ({ children }: { children?: ReactNode }) => (
|
|
116
|
+
<Callout type="info" className="velu-callout velu-callout-info">{children}</Callout>
|
|
117
|
+
),
|
|
118
|
+
Tip: ({ children }: { children?: ReactNode }) => (
|
|
119
|
+
<Callout type="idea" className="velu-callout velu-callout-idea">{children}</Callout>
|
|
120
|
+
),
|
|
121
|
+
Check: ({ children }: { children?: ReactNode }) => (
|
|
122
|
+
<Callout type="success" className="velu-callout velu-callout-success">{children}</Callout>
|
|
123
|
+
),
|
|
124
|
+
Danger: ({ children }: { children?: ReactNode }) => (
|
|
125
|
+
<Callout type="error" className="velu-callout velu-callout-error">{children}</Callout>
|
|
126
|
+
),
|
|
127
|
+
// Mint uses `CardGroup`; Fumadocs uses `Cards`.
|
|
128
|
+
CardGroup: ({ cols, className, style, ...props }: any) => (
|
|
129
|
+
<Cards
|
|
130
|
+
{...props}
|
|
131
|
+
className={[
|
|
132
|
+
'velu-card-group',
|
|
133
|
+
cols === 1 ? 'velu-card-group-cols-1' : '',
|
|
134
|
+
className,
|
|
135
|
+
].filter(Boolean).join(' ')}
|
|
136
|
+
style={{ ...style, ...(cols ? { ['--velu-card-cols' as any]: String(cols) } : {}) }}
|
|
137
|
+
/>
|
|
138
|
+
),
|
|
139
|
+
CardDeck: (props: any) => <Cards {...props} className={['velu-card-deck', props.className].filter(Boolean).join(' ')} />,
|
|
140
|
+
// Mint compatibility for Card icon strings + horizontal layout.
|
|
141
|
+
Card: ({
|
|
142
|
+
horizontal,
|
|
143
|
+
icon,
|
|
144
|
+
iconType,
|
|
145
|
+
img,
|
|
146
|
+
image,
|
|
147
|
+
color,
|
|
148
|
+
cta,
|
|
149
|
+
arrow,
|
|
150
|
+
className,
|
|
151
|
+
children,
|
|
152
|
+
...props
|
|
153
|
+
}: any) => {
|
|
154
|
+
const hasImage = Boolean(img || image);
|
|
155
|
+
const hasIcon = Boolean(icon);
|
|
156
|
+
// Mintlify-like default: icon cards are horizontal unless explicitly disabled.
|
|
157
|
+
const useHorizontal = typeof horizontal === 'boolean' ? horizontal : (hasIcon && !hasImage);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Card
|
|
161
|
+
{...props}
|
|
162
|
+
icon={typeof icon === 'string'
|
|
163
|
+
? (
|
|
164
|
+
<VeluIcon
|
|
165
|
+
name={icon}
|
|
166
|
+
library={iconLibrary}
|
|
167
|
+
iconType={iconType}
|
|
168
|
+
color={typeof color === 'string' && color.startsWith('#') ? color : undefined}
|
|
169
|
+
/>
|
|
170
|
+
)
|
|
171
|
+
: icon}
|
|
172
|
+
className={[
|
|
173
|
+
'velu-card',
|
|
174
|
+
className,
|
|
175
|
+
useHorizontal ? 'velu-card-horizontal' : '',
|
|
176
|
+
(color && !(typeof color === 'string' && color.startsWith('#')))
|
|
177
|
+
? `velu-card-color-${String(color)}`
|
|
178
|
+
: '',
|
|
179
|
+
].filter(Boolean).join(' ')}
|
|
180
|
+
>
|
|
181
|
+
{hasImage ? (
|
|
182
|
+
<img src={img ?? image} alt="" className="velu-card-image" />
|
|
183
|
+
) : null}
|
|
184
|
+
{children}
|
|
185
|
+
{(cta || arrow) ? (
|
|
186
|
+
<div className="velu-card-cta">
|
|
187
|
+
{cta ? <span>{cta}</span> : null}
|
|
188
|
+
{arrow ? <VeluIcon name="arrow-right" library={iconLibrary} className="velu-card-cta-arrow" /> : null}
|
|
189
|
+
</div>
|
|
190
|
+
) : null}
|
|
191
|
+
</Card>
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
Accordions: FumaAccordions as any,
|
|
195
|
+
Accordion: FumaAccordion as any,
|
|
196
|
+
CodeGroup: VeluCodeGroup as any,
|
|
197
|
+
Frame: ({ children, caption, hint, className }: any) => (
|
|
198
|
+
<>
|
|
199
|
+
{hint ? (
|
|
200
|
+
<div className="velu-frame-hint">
|
|
201
|
+
<VeluIcon name={hintIconName} library={iconLibrary} className="velu-frame-hint-icon" />
|
|
202
|
+
<span>{hint}</span>
|
|
203
|
+
</div>
|
|
204
|
+
) : null}
|
|
205
|
+
<figure className={['velu-frame', className].filter(Boolean).join(' ')}>
|
|
206
|
+
<div className="velu-frame-content">{children}</div>
|
|
207
|
+
{caption ? (
|
|
208
|
+
<figcaption>
|
|
209
|
+
{typeof caption === 'string' ? (
|
|
210
|
+
<span
|
|
211
|
+
dangerouslySetInnerHTML={{
|
|
212
|
+
__html: caption
|
|
213
|
+
.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
|
|
214
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>'),
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
) : (
|
|
218
|
+
caption
|
|
219
|
+
)}
|
|
220
|
+
</figcaption>
|
|
221
|
+
) : null}
|
|
222
|
+
</figure>
|
|
223
|
+
</>
|
|
224
|
+
),
|
|
225
|
+
Badge: ({
|
|
226
|
+
children,
|
|
227
|
+
className,
|
|
228
|
+
color = 'gray',
|
|
229
|
+
size = 'md',
|
|
230
|
+
shape = 'rounded',
|
|
231
|
+
round,
|
|
232
|
+
icon,
|
|
233
|
+
stroke,
|
|
234
|
+
disabled,
|
|
235
|
+
iconType,
|
|
236
|
+
}: any) => (
|
|
237
|
+
<span
|
|
238
|
+
className={[
|
|
239
|
+
'velu-badge',
|
|
240
|
+
`velu-badge-color-${String(color)}`,
|
|
241
|
+
`velu-badge-size-${String(size)}`,
|
|
242
|
+
`velu-badge-shape-${String(round ? 'pill' : shape)}`,
|
|
243
|
+
stroke ? 'velu-badge-stroke' : '',
|
|
244
|
+
disabled ? 'velu-badge-disabled' : '',
|
|
245
|
+
className,
|
|
246
|
+
].filter(Boolean).join(' ')}
|
|
247
|
+
>
|
|
248
|
+
{icon ? (
|
|
249
|
+
<span className="velu-badge-icon">
|
|
250
|
+
<VeluIcon name={String(icon)} library={iconLibrary} iconType={iconType} />
|
|
251
|
+
</span>
|
|
252
|
+
) : null}
|
|
253
|
+
<span>{children}</span>
|
|
254
|
+
</span>
|
|
255
|
+
),
|
|
256
|
+
Banner: ({ children, className, color = 'default', href, icon, iconType }: any) => {
|
|
257
|
+
const content = (
|
|
258
|
+
<>
|
|
259
|
+
{icon ? (
|
|
260
|
+
<span className="velu-banner-icon">
|
|
261
|
+
<VeluIcon name={String(icon)} library={iconLibrary} iconType={iconType} />
|
|
262
|
+
</span>
|
|
263
|
+
) : null}
|
|
264
|
+
<div className="velu-banner-content">{children}</div>
|
|
265
|
+
</>
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const classes = ['velu-banner', `velu-banner-${String(color)}`, className].filter(Boolean).join(' ');
|
|
269
|
+
if (href) {
|
|
270
|
+
return (
|
|
271
|
+
<a href={href} className={classes}>
|
|
272
|
+
{content}
|
|
273
|
+
</a>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return <div className={classes}>{content}</div>;
|
|
278
|
+
},
|
|
279
|
+
Color: VeluColor as any,
|
|
280
|
+
'Color.Item': VeluColorItem as any,
|
|
281
|
+
'Color.Row': VeluColorRow as any,
|
|
282
|
+
ColorItem: VeluColorItem as any,
|
|
283
|
+
ColorRow: VeluColorRow as any,
|
|
284
|
+
Columns: ({ children, className, cols, style }: any) => (
|
|
285
|
+
<div
|
|
286
|
+
className={['velu-columns', className].filter(Boolean).join(' ')}
|
|
287
|
+
style={{ ...style, ...(cols ? { ['--velu-columns-count' as any]: String(cols) } : {}) }}
|
|
288
|
+
>
|
|
289
|
+
{children}
|
|
290
|
+
</div>
|
|
291
|
+
),
|
|
292
|
+
Column: ({ children, className }: any) => (
|
|
293
|
+
<div className={['velu-column', className].filter(Boolean).join(' ')}>{children}</div>
|
|
294
|
+
),
|
|
295
|
+
Examples: ({ children, className }: any) => (
|
|
296
|
+
<div className={['velu-examples', className].filter(Boolean).join(' ')}>{children}</div>
|
|
297
|
+
),
|
|
298
|
+
Panel: ({ title, children, className }: any) => (
|
|
299
|
+
<aside className={['velu-panel', className].filter(Boolean).join(' ')}>
|
|
300
|
+
{title ? <h4>{title}</h4> : null}
|
|
301
|
+
{children}
|
|
302
|
+
</aside>
|
|
303
|
+
),
|
|
304
|
+
Prompt: VeluPrompt as any,
|
|
305
|
+
Response: ({ children, className }: any) => (
|
|
306
|
+
<section className={['velu-response', className].filter(Boolean).join(' ')}>{children}</section>
|
|
307
|
+
),
|
|
308
|
+
Tabs: VeluSyncedTabs as any,
|
|
309
|
+
Tab: ({ title, icon, iconType, className, ...props }: any) => (
|
|
310
|
+
<FumaTab
|
|
311
|
+
{...props}
|
|
312
|
+
data-title={typeof title === 'string' ? title : undefined}
|
|
313
|
+
className={['!p-0 !bg-transparent !rounded-none !border-0', className].filter(Boolean).join(' ')}
|
|
314
|
+
/>
|
|
315
|
+
),
|
|
316
|
+
Steps: FumaSteps as any,
|
|
317
|
+
Step: ({ title, children, ...props }: any) => (
|
|
318
|
+
<FumaStep {...props}>
|
|
319
|
+
{title ? <p className="velu-step-title">{title}</p> : null}
|
|
320
|
+
{children}
|
|
321
|
+
</FumaStep>
|
|
322
|
+
),
|
|
323
|
+
Expandable: VeluExpandable as any,
|
|
324
|
+
ParamField: ({
|
|
325
|
+
body,
|
|
326
|
+
query,
|
|
327
|
+
path,
|
|
328
|
+
header,
|
|
329
|
+
id,
|
|
330
|
+
type,
|
|
331
|
+
required,
|
|
332
|
+
deprecated,
|
|
333
|
+
default: defaultProp,
|
|
334
|
+
children,
|
|
335
|
+
className,
|
|
336
|
+
...props
|
|
337
|
+
}: any) => {
|
|
338
|
+
const locationPairs: Array<['query' | 'path' | 'body' | 'header', unknown]> = [
|
|
339
|
+
['query', query],
|
|
340
|
+
['path', path],
|
|
341
|
+
['body', body],
|
|
342
|
+
['header', header],
|
|
343
|
+
];
|
|
344
|
+
const location = locationPairs.find(([, value]) => Boolean(value));
|
|
345
|
+
const locationKey = location?.[0];
|
|
346
|
+
const locationValue = location?.[1];
|
|
347
|
+
const fieldName = typeof locationValue === 'string' && locationValue.trim()
|
|
348
|
+
? locationValue.trim()
|
|
349
|
+
: undefined;
|
|
350
|
+
const anchorBase = (fieldName ?? 'param').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
351
|
+
const anchorId = typeof id === 'string' && id.trim() ? id.trim() : `param-${anchorBase || 'param'}`;
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<section id={anchorId} className={['velu-param-field-item', className].filter(Boolean).join(' ')} {...props}>
|
|
355
|
+
<div className="velu-param-head">
|
|
356
|
+
<a className="velu-param-anchor" href={`#${anchorId}`} aria-label={`Anchor for ${fieldName ?? 'param'}`}>
|
|
357
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
358
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
359
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
360
|
+
</svg>
|
|
361
|
+
</a>
|
|
362
|
+
<code>{fieldName ?? 'param'}</code>
|
|
363
|
+
{type ? <span className="velu-pill velu-pill-type">{String(type)}</span> : null}
|
|
364
|
+
{required ? <span className="velu-pill velu-pill-required">required</span> : null}
|
|
365
|
+
{deprecated ? <span className="velu-pill velu-pill-deprecated">deprecated</span> : null}
|
|
366
|
+
{defaultProp != null ? <em>default: {String(defaultProp)}</em> : null}
|
|
367
|
+
</div>
|
|
368
|
+
{children ? <div className="velu-param-body">{children}</div> : null}
|
|
369
|
+
</section>
|
|
370
|
+
);
|
|
371
|
+
},
|
|
372
|
+
Param: ({ name, type, required, defaultValue, children, className }: any) => (
|
|
373
|
+
<div className={['velu-param', className].filter(Boolean).join(' ')}>
|
|
374
|
+
<div className="velu-param-head">
|
|
375
|
+
<code>{name ?? 'param'}</code>
|
|
376
|
+
{type ? <span>{type}</span> : null}
|
|
377
|
+
{required ? <strong>required</strong> : null}
|
|
378
|
+
{defaultValue ? <em>default: {String(defaultValue)}</em> : null}
|
|
379
|
+
</div>
|
|
380
|
+
{children ? <div className="velu-param-body">{children}</div> : null}
|
|
381
|
+
</div>
|
|
382
|
+
),
|
|
383
|
+
RequestExample: ({ children, className, ...props }: any) => (
|
|
384
|
+
<section className={['velu-request-example', className].filter(Boolean).join(' ')} {...props}>
|
|
385
|
+
<FumaTabs items={['Request']}>
|
|
386
|
+
<FumaTab>{children}</FumaTab>
|
|
387
|
+
</FumaTabs>
|
|
388
|
+
</section>
|
|
389
|
+
),
|
|
390
|
+
ResponseField: ({
|
|
391
|
+
id,
|
|
392
|
+
name,
|
|
393
|
+
type,
|
|
394
|
+
required,
|
|
395
|
+
deprecated,
|
|
396
|
+
default: defaultProp,
|
|
397
|
+
defaultValue,
|
|
398
|
+
pre,
|
|
399
|
+
post,
|
|
400
|
+
children,
|
|
401
|
+
className,
|
|
402
|
+
...props
|
|
403
|
+
}: any) => {
|
|
404
|
+
const preTokens = toTokenList(pre);
|
|
405
|
+
const postTokens = toTokenList(post);
|
|
406
|
+
const resolvedDefault = defaultProp ?? defaultValue;
|
|
407
|
+
const hasFieldProps = Boolean(name || type || required || deprecated || preTokens.length || postTokens.length || resolvedDefault != null);
|
|
408
|
+
const anchorBase = String(name ?? 'response').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
409
|
+
const anchorId = typeof id === 'string' && id.trim() ? id.trim() : `response-${anchorBase || 'field'}`;
|
|
410
|
+
|
|
411
|
+
if (!hasFieldProps) {
|
|
412
|
+
return (
|
|
413
|
+
<section className={['velu-response-field', className].filter(Boolean).join(' ')} {...props}>
|
|
414
|
+
{children}
|
|
415
|
+
</section>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<section id={anchorId} className={['velu-response-field-item', className].filter(Boolean).join(' ')} {...props}>
|
|
421
|
+
<div className="velu-property-head">
|
|
422
|
+
<a className="velu-param-anchor" href={`#${anchorId}`} aria-label={`Anchor for ${name ?? 'response field'}`}>
|
|
423
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
424
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
425
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
426
|
+
</svg>
|
|
427
|
+
</a>
|
|
428
|
+
{preTokens.map((token, index) => (
|
|
429
|
+
<span key={`pre-${token}-${index}`} className="velu-pill velu-pill-type">{token}</span>
|
|
430
|
+
))}
|
|
431
|
+
<code>{name ?? 'response'}</code>
|
|
432
|
+
{type ? <span className="velu-pill velu-pill-type">{String(type)}</span> : null}
|
|
433
|
+
{required ? <span className="velu-pill velu-pill-required">required</span> : null}
|
|
434
|
+
{deprecated ? <span className="velu-pill velu-pill-deprecated">deprecated</span> : null}
|
|
435
|
+
{resolvedDefault != null ? <em>default: {String(resolvedDefault)}</em> : null}
|
|
436
|
+
{postTokens.map((token, index) => (
|
|
437
|
+
<span key={`post-${token}-${index}`} className="velu-pill velu-pill-type">{token}</span>
|
|
438
|
+
))}
|
|
439
|
+
</div>
|
|
440
|
+
{children ? <div className="velu-property-body">{children}</div> : null}
|
|
441
|
+
</section>
|
|
442
|
+
);
|
|
443
|
+
},
|
|
444
|
+
ResponseExample: ({ children, className, ...props }: any) => (
|
|
445
|
+
<section className={['velu-response-example', className].filter(Boolean).join(' ')} {...props}>
|
|
446
|
+
<FumaTabs items={['Response']}>
|
|
447
|
+
<FumaTab>{children}</FumaTab>
|
|
448
|
+
</FumaTabs>
|
|
449
|
+
</section>
|
|
450
|
+
),
|
|
451
|
+
Properties: ({ children, className }: any) => (
|
|
452
|
+
<section className={['velu-properties', className].filter(Boolean).join(' ')}>{children}</section>
|
|
453
|
+
),
|
|
454
|
+
Property: ({ name, type, required, children, className }: any) => (
|
|
455
|
+
<div className={['velu-property', className].filter(Boolean).join(' ')}>
|
|
456
|
+
<div className="velu-property-head">
|
|
457
|
+
<code>{name ?? 'property'}</code>
|
|
458
|
+
{type ? <span>{type}</span> : null}
|
|
459
|
+
{required ? <strong>required</strong> : null}
|
|
460
|
+
</div>
|
|
461
|
+
{children ? <div className="velu-property-body">{children}</div> : null}
|
|
462
|
+
</div>
|
|
463
|
+
),
|
|
464
|
+
Endpoint: ({ method = 'GET', path, children, className }: any) => (
|
|
465
|
+
<section className={['velu-endpoint', className].filter(Boolean).join(' ')}>
|
|
466
|
+
<div className="velu-endpoint-head">
|
|
467
|
+
<span>{String(method).toUpperCase()}</span>
|
|
468
|
+
<code>{path ?? '/'}</code>
|
|
469
|
+
</div>
|
|
470
|
+
{children ? <div className="velu-endpoint-body">{children}</div> : null}
|
|
471
|
+
</section>
|
|
472
|
+
),
|
|
473
|
+
APIPlayground: ({ endpoint, method = 'GET', spec, src, path, url, proxyUrl, className }: any) => (
|
|
474
|
+
<VeluOpenAPI
|
|
475
|
+
className={['velu-api-playground', className].filter(Boolean).join(' ')}
|
|
476
|
+
schemaSource={String(spec ?? src ?? path ?? url ?? '/api-reference/openapi-example.json')}
|
|
477
|
+
endpoint={String(endpoint ?? '/')}
|
|
478
|
+
method={method}
|
|
479
|
+
proxyUrl={typeof proxyUrl === 'string' ? proxyUrl : defaultProxyUrl}
|
|
480
|
+
exampleLanguages={apiConfig.exampleLanguages}
|
|
481
|
+
exampleAutogenerate={apiConfig.exampleAutogenerate}
|
|
482
|
+
/>
|
|
483
|
+
),
|
|
484
|
+
ApiPlayground: ({ endpoint, method = 'GET', spec, src, path, url, proxyUrl, className }: any) => (
|
|
485
|
+
<VeluOpenAPI
|
|
486
|
+
className={['velu-api-playground', className].filter(Boolean).join(' ')}
|
|
487
|
+
schemaSource={String(spec ?? src ?? path ?? url ?? '/api-reference/openapi-example.json')}
|
|
488
|
+
endpoint={String(endpoint ?? '/')}
|
|
489
|
+
method={method}
|
|
490
|
+
proxyUrl={typeof proxyUrl === 'string' ? proxyUrl : defaultProxyUrl}
|
|
491
|
+
exampleLanguages={apiConfig.exampleLanguages}
|
|
492
|
+
exampleAutogenerate={apiConfig.exampleAutogenerate}
|
|
493
|
+
/>
|
|
494
|
+
),
|
|
495
|
+
OpenAPI: ({ src, path, spec, url, proxyUrl, className }: any) => (
|
|
496
|
+
<VeluOpenAPI
|
|
497
|
+
className={['velu-openapi', className].filter(Boolean).join(' ')}
|
|
498
|
+
schemaSource={String(spec ?? src ?? path ?? url ?? '/api-reference/openapi-example.json')}
|
|
499
|
+
proxyUrl={typeof proxyUrl === 'string' ? proxyUrl : defaultProxyUrl}
|
|
500
|
+
exampleLanguages={apiConfig.exampleLanguages}
|
|
501
|
+
exampleAutogenerate={apiConfig.exampleAutogenerate}
|
|
502
|
+
showTitle
|
|
503
|
+
showDescription
|
|
504
|
+
/>
|
|
505
|
+
),
|
|
506
|
+
OpenApiSchema: ({ src, path, spec, url, name, schema, className }: any) => (
|
|
507
|
+
<VeluOpenAPISchema
|
|
508
|
+
className={['velu-openapi-schema-wrapper', className].filter(Boolean).join(' ')}
|
|
509
|
+
schemaSource={String(spec ?? src ?? path ?? url ?? '/api-reference/openapi-example.json')}
|
|
510
|
+
schema={String(schema ?? name ?? '')}
|
|
511
|
+
/>
|
|
512
|
+
),
|
|
513
|
+
OpenAPISchema: ({ src, path, spec, url, name, schema, className }: any) => (
|
|
514
|
+
<VeluOpenAPISchema
|
|
515
|
+
className={['velu-openapi-schema-wrapper', className].filter(Boolean).join(' ')}
|
|
516
|
+
schemaSource={String(spec ?? src ?? path ?? url ?? '/api-reference/openapi-example.json')}
|
|
517
|
+
schema={String(schema ?? name ?? '')}
|
|
518
|
+
/>
|
|
519
|
+
),
|
|
520
|
+
Snippet: ({ id, name, title, children, className }: any) => (
|
|
521
|
+
<section className={['velu-snippet', className].filter(Boolean).join(' ')}>
|
|
522
|
+
{title ? <h4>{title}</h4> : null}
|
|
523
|
+
{children ?? <p>Snippet: <code>{id ?? name ?? 'snippet'}</code></p>}
|
|
524
|
+
</section>
|
|
525
|
+
),
|
|
526
|
+
Video: ({ src, title, className }: any) => (
|
|
527
|
+
<div className={['velu-video', className].filter(Boolean).join(' ')}>
|
|
528
|
+
{src ? (
|
|
529
|
+
<iframe
|
|
530
|
+
src={src}
|
|
531
|
+
title={title ?? 'Video'}
|
|
532
|
+
loading="lazy"
|
|
533
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
534
|
+
allowFullScreen
|
|
535
|
+
/>
|
|
536
|
+
) : null}
|
|
537
|
+
</div>
|
|
538
|
+
),
|
|
539
|
+
Update: ({ label, date, description, tags, rss, children, className, ...props }: any) => {
|
|
540
|
+
void rss;
|
|
541
|
+
const updateLabel = String(label ?? date ?? 'Update');
|
|
542
|
+
const updateDescription = description != null ? String(description) : undefined;
|
|
543
|
+
const updateTags = toTokenList(tags);
|
|
544
|
+
const anchorBase = updateLabel.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
545
|
+
const anchorId = `update-${anchorBase || 'item'}`;
|
|
546
|
+
|
|
547
|
+
return (
|
|
548
|
+
<section
|
|
549
|
+
id={anchorId}
|
|
550
|
+
data-update-label={updateLabel}
|
|
551
|
+
data-update-tags={updateTags.join('|')}
|
|
552
|
+
className={['velu-update', className].filter(Boolean).join(' ')}
|
|
553
|
+
{...props}
|
|
554
|
+
>
|
|
555
|
+
<div className="velu-update-meta">
|
|
556
|
+
<a className="velu-update-anchor" href={`#${anchorId}`} aria-label={`Anchor for ${updateLabel}`}>
|
|
557
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
558
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
559
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
560
|
+
</svg>
|
|
561
|
+
</a>
|
|
562
|
+
<a className="velu-update-label" href={`#${anchorId}`}>{updateLabel}</a>
|
|
563
|
+
{updateDescription ? <div className="velu-update-description">{updateDescription}</div> : null}
|
|
564
|
+
{updateTags.length ? (
|
|
565
|
+
<div className="velu-update-tags">
|
|
566
|
+
{updateTags.map((tag, index) => (
|
|
567
|
+
<span key={`${tag}-${index}`} className="velu-update-tag">{tag}</span>
|
|
568
|
+
))}
|
|
569
|
+
</div>
|
|
570
|
+
) : null}
|
|
571
|
+
</div>
|
|
572
|
+
{children ? <div className="velu-update-content">{children}</div> : null}
|
|
573
|
+
</section>
|
|
574
|
+
);
|
|
575
|
+
},
|
|
576
|
+
Tooltip: ({ tip, text, content, headline, cta, href, children, className }: any) => {
|
|
577
|
+
const tooltipBody = tip ?? text ?? content ?? '';
|
|
578
|
+
const hasCard = Boolean(headline || tooltipBody || cta);
|
|
579
|
+
return (
|
|
580
|
+
<span className={['velu-tooltip-wrap', className].filter(Boolean).join(' ')}>
|
|
581
|
+
<span className="velu-tooltip" title={tooltipBody}>
|
|
582
|
+
{children}
|
|
583
|
+
</span>
|
|
584
|
+
{hasCard ? (
|
|
585
|
+
<span className="velu-tooltip-popover" role="tooltip">
|
|
586
|
+
{headline ? <span className="velu-tooltip-headline">{headline}</span> : null}
|
|
587
|
+
{tooltipBody ? <span className="velu-tooltip-text">{tooltipBody}</span> : null}
|
|
588
|
+
{cta ? (
|
|
589
|
+
href ? (
|
|
590
|
+
<a href={href} className="velu-tooltip-cta">{cta}</a>
|
|
591
|
+
) : (
|
|
592
|
+
<span className="velu-tooltip-cta">{cta}</span>
|
|
593
|
+
)
|
|
594
|
+
) : null}
|
|
595
|
+
</span>
|
|
596
|
+
) : null}
|
|
597
|
+
</span>
|
|
598
|
+
);
|
|
599
|
+
},
|
|
600
|
+
Tiles: ({ children, className }: any) => (
|
|
601
|
+
<div className={['velu-tiles', className].filter(Boolean).join(' ')}>{children}</div>
|
|
602
|
+
),
|
|
603
|
+
Tile: ({ title, href, description, children, className, ...props }: any) => {
|
|
604
|
+
const isPlainTextChild = typeof children === 'string' || typeof children === 'number';
|
|
605
|
+
const resolvedDescription = description ?? (isPlainTextChild ? String(children) : undefined);
|
|
606
|
+
const preview = isPlainTextChild ? null : children;
|
|
607
|
+
|
|
608
|
+
return (
|
|
609
|
+
<a href={href ?? '#'} className={['velu-tile', className].filter(Boolean).join(' ')} {...props}>
|
|
610
|
+
{preview ? <span className="velu-tile-preview">{preview}</span> : null}
|
|
611
|
+
{(title || resolvedDescription) ? (
|
|
612
|
+
<span className="velu-tile-body">
|
|
613
|
+
{title ? <strong className="velu-tile-title">{title}</strong> : null}
|
|
614
|
+
{resolvedDescription ? <span className="velu-tile-description">{resolvedDescription}</span> : null}
|
|
615
|
+
</span>
|
|
616
|
+
) : null}
|
|
617
|
+
</a>
|
|
618
|
+
);
|
|
619
|
+
},
|
|
620
|
+
Tree: VeluTree as any,
|
|
621
|
+
'Tree.Folder': VeluTreeFolder as any,
|
|
622
|
+
'Tree.File': VeluTreeFile as any,
|
|
623
|
+
Files: VeluFiles as any,
|
|
624
|
+
Folder: VeluFilesFolder as any,
|
|
625
|
+
File: VeluTreeFile as any,
|
|
626
|
+
'Files.Folder': VeluFilesFolder as any,
|
|
627
|
+
'Files.File': VeluTreeFile as any,
|
|
628
|
+
View: VeluView as any,
|
|
629
|
+
Icon: ({ icon, name, iconType, color, size, className }: any) => {
|
|
630
|
+
const pxSize = typeof size === 'number' && Number.isFinite(size) ? size : undefined;
|
|
631
|
+
const style = pxSize ? { width: `${pxSize}px`, height: `${pxSize}px` } : undefined;
|
|
632
|
+
|
|
633
|
+
if (isValidElement(icon)) {
|
|
634
|
+
const iconEl = icon as ReactElement<{ className?: string; style?: CSSProperties }>;
|
|
635
|
+
return (
|
|
636
|
+
<span className={['velu-inline-icon', className].filter(Boolean).join(' ')} style={style}>
|
|
637
|
+
{cloneElement(iconEl, {
|
|
638
|
+
className: [iconEl.props?.className, 'velu-inline-icon-svg'].filter(Boolean).join(' '),
|
|
639
|
+
style: {
|
|
640
|
+
...(iconEl.props?.style ?? {}),
|
|
641
|
+
...(color ? { color } : {}),
|
|
642
|
+
},
|
|
643
|
+
})}
|
|
644
|
+
</span>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const iconName = typeof icon === 'string' ? icon : String(name ?? 'circle-help');
|
|
649
|
+
return (
|
|
650
|
+
<span className={['velu-inline-icon', className].filter(Boolean).join(' ')} style={style}>
|
|
651
|
+
<VeluIcon name={iconName} library={iconLibrary} iconType={iconType} color={color} />
|
|
652
|
+
</span>
|
|
653
|
+
);
|
|
654
|
+
},
|
|
655
|
+
Mermaid: VeluMermaid as any,
|
|
8
656
|
};
|
|
9
657
|
}
|