@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.
Files changed (60) hide show
  1. package/package.json +15 -6
  2. package/schema/velu.schema.json +1251 -115
  3. package/src/build.ts +1121 -304
  4. package/src/cli.ts +90 -26
  5. package/src/engine/_server.mjs +1684 -277
  6. package/src/engine/app/(docs)/[...slug]/layout.tsx +371 -0
  7. package/src/engine/app/(docs)/[...slug]/page.tsx +926 -0
  8. package/src/engine/app/api/proxy/route.ts +23 -0
  9. package/src/engine/app/copy-page.css +59 -1
  10. package/src/engine/app/global.css +3157 -3
  11. package/src/engine/app/layout.tsx +56 -1
  12. package/src/engine/app/llms-file/route.ts +87 -0
  13. package/src/engine/app/llms-full-file/route.ts +62 -0
  14. package/src/engine/app/md-file/[...slug]/route.ts +409 -0
  15. package/src/engine/app/page.tsx +45 -0
  16. package/src/engine/app/robots.txt/route.ts +63 -0
  17. package/src/engine/app/rss-file/[...slug]/route.ts +169 -0
  18. package/src/engine/app/sitemap.xml/route.ts +82 -0
  19. package/src/engine/components/assistant.tsx +16 -5
  20. package/src/engine/components/changelog-filters.tsx +114 -0
  21. package/src/engine/components/code-group.tsx +383 -0
  22. package/src/engine/components/color.tsx +118 -0
  23. package/src/engine/components/expandable.tsx +77 -0
  24. package/src/engine/components/icon.tsx +136 -0
  25. package/src/engine/components/image-zoom-fallback.tsx +147 -0
  26. package/src/engine/components/image.tsx +111 -0
  27. package/src/engine/components/manual-api-playground.tsx +154 -0
  28. package/src/engine/components/mermaid.tsx +142 -0
  29. package/src/engine/components/openapi-toc-sync.tsx +59 -0
  30. package/src/engine/components/openapi.tsx +1682 -0
  31. package/src/engine/components/page-feedback.tsx +153 -0
  32. package/src/engine/components/product-switcher.tsx +27 -3
  33. package/src/engine/components/prompt.tsx +90 -0
  34. package/src/engine/components/providers.tsx +1 -6
  35. package/src/engine/components/search.tsx +4 -0
  36. package/src/engine/components/sidebar-links.tsx +13 -15
  37. package/src/engine/components/synced-tabs.tsx +57 -0
  38. package/src/engine/components/toc-examples.tsx +110 -0
  39. package/src/engine/components/view.tsx +344 -0
  40. package/src/engine/generated/redirects.ts +3 -0
  41. package/src/engine/lib/changelog.ts +246 -0
  42. package/src/engine/lib/layout.shared.ts +30 -2
  43. package/src/engine/lib/llms.ts +444 -0
  44. package/src/engine/lib/navigation-normalize.mjs +481 -412
  45. package/src/engine/lib/navigation-normalize.ts +261 -54
  46. package/src/engine/lib/redirects.ts +194 -0
  47. package/src/engine/lib/source.ts +107 -4
  48. package/src/engine/lib/velu.ts +368 -2
  49. package/src/engine/mdx-components.tsx +648 -0
  50. package/src/engine/middleware.ts +66 -0
  51. package/src/engine/public/icons/cursor-dark.svg +12 -0
  52. package/src/engine/public/icons/cursor-light.svg +12 -0
  53. package/src/engine/source.config.ts +98 -1
  54. package/src/engine/src/components/PageTitle.astro +16 -5
  55. package/src/engine/src/lib/velu.ts +11 -3
  56. package/src/navigation-normalize.ts +252 -54
  57. package/src/themes.ts +6 -6
  58. package/src/validate.ts +119 -6
  59. package/src/engine/app/(docs)/[[...slug]]/layout.tsx +0 -87
  60. 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
  }