@aravindc26/velu 0.12.7 → 0.12.9

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