@veluai/velu 0.2.36 → 0.2.38
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/README.md +17 -0
- package/dist/cli.js +122 -55
- package/docs/mintlify-migration.md +159 -0
- package/docs/vepa.md +97 -0
- package/package.json +8 -2
- package/runtime/velu-ui/components/ApiReferencePage.jsx +11 -3
- package/runtime/velu-ui/components/ApiSamples.jsx +9 -4
- package/runtime/velu-ui/components/Card.jsx +7 -4
- package/runtime/velu-ui/components/Chatbot.jsx +9 -4
- package/runtime/velu-ui/components/Columns.jsx +1 -0
- package/runtime/velu-ui/components/PageFooter.jsx +3 -0
- package/runtime/velu-ui/components/PageHeader.jsx +14 -7
- package/runtime/velu-ui/components/ThemePreferenceMenu.jsx +57 -0
- package/runtime/velu-ui/components/ThemeToggle.jsx +3 -1
- package/runtime/velu-ui/components/Update.jsx +22 -10
- package/runtime/velu-ui/components/VepaFooter.jsx +40 -0
- package/runtime/velu-ui/components/card.css +9 -0
- package/runtime/velu-ui/components/chatbot.css +42 -0
- package/runtime/velu-ui/components/docs-layout.css +75 -0
- package/runtime/velu-ui/components/page-header.css +6 -0
- package/runtime/velu-ui/styles.css +1 -0
- package/runtime/velu-ui/themes/vepa.css +396 -0
- package/schema/velu.schema.json +5 -0
- package/src/navigation.js +3 -2
- package/src/runtime/App.jsx +120 -50
- package/src/runtime/VepaToc.jsx +12 -0
- package/src/runtime/page-mode.js +54 -0
- package/templates/starter/essentials/settings.mdx +15 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Migrate Mintlify documentation to Velu
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
velu migrate mintlify ./mintlify-docs --out ./velu-docs --dry-run
|
|
5
|
+
velu migrate mintlify ./mintlify-docs --out ./velu-docs
|
|
6
|
+
cd velu-docs
|
|
7
|
+
velu validate .
|
|
8
|
+
velu dev .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
From this repository, replace `velu` with `node packages/velu-cli/bin/cli.js`.
|
|
12
|
+
The command is included in the bundled public CLI. It has not been published
|
|
13
|
+
until a package release is made.
|
|
14
|
+
|
|
15
|
+
The source is a local repository/content directory containing `docs.json` or
|
|
16
|
+
legacy `mint.json`. The command creates a **new directory** and leaves the source
|
|
17
|
+
untouched. Without `--out`, it creates a sibling named `<source>-velu`. Existing
|
|
18
|
+
destinations, destinations inside the source, and filename collisions are rejected.
|
|
19
|
+
Files are staged before the completed directory is moved into place.
|
|
20
|
+
|
|
21
|
+
`docs.json` takes precedence if both configs exist. Use `--config mint.json` to
|
|
22
|
+
select the legacy file. JSON comments/trailing commas and local JSON/YAML config
|
|
23
|
+
`$ref` files are supported. Config references cannot escape the source directory;
|
|
24
|
+
remote references, fragments, and cycles fail with an explicit error.
|
|
25
|
+
|
|
26
|
+
## Preview and CI
|
|
27
|
+
|
|
28
|
+
| Option | Behavior |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `--dry-run` | Runs analysis, transformations, and MDX compilation in memory; writes nothing |
|
|
31
|
+
| `--json` | Prints only the machine-readable report |
|
|
32
|
+
| `--strict` | Refuses to write output if there are warnings or errors |
|
|
33
|
+
| `--out <directory>` | Sets the new destination |
|
|
34
|
+
| `--config docs.json\|mint.json` | Selects the source configuration |
|
|
35
|
+
| `--help` | Prints usage |
|
|
36
|
+
|
|
37
|
+
Exit codes are `0` for completion without compatibility errors, `1` for invocation
|
|
38
|
+
or filesystem failures, `2` for compatibility errors, and `3` when strict mode
|
|
39
|
+
requires review. **Normal mode writes a reviewable migration even with code 2.**
|
|
40
|
+
Warnings alone return 0 unless strict mode is enabled. The report still labels
|
|
41
|
+
any migration with findings `needs-review`.
|
|
42
|
+
|
|
43
|
+
Every migration writes `VELU-MIGRATION.md` and `velu-migration-report.json`.
|
|
44
|
+
It also archives the fully resolved original config in
|
|
45
|
+
`.velu-migration/source-config.json`, outside the publicly served assets.
|
|
46
|
+
The JSON report includes per-file actions and SHA-256 output hashes, script
|
|
47
|
+
conversions, skipped paths, and findings with source locations. Reports do not
|
|
48
|
+
contain source script bodies or unsupported config values. They can contain
|
|
49
|
+
filenames, endpoint paths, and diagnostic text; review before sharing.
|
|
50
|
+
|
|
51
|
+
## Coverage
|
|
52
|
+
|
|
53
|
+
| Area | Migration behavior |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Config | Projects supported values against the shipped Velu schema; reports unsupported nested fields individually and preserves the original source config |
|
|
56
|
+
| Legacy config | Converts navigation arrays, prefix-based tabs, anchors, topbar links/CTA, footer socials, and API base URL/example languages |
|
|
57
|
+
| Navigation | Retains groups, tabs, products, versions, languages, root pages, and supported OpenAPI references; maps simple dropdown containers to tabs and merges global anchors/tabs with a review finding |
|
|
58
|
+
| Branding | Copies logos/favicons, colors, navbar/footer, contextual menu and SEO; maps `description` to SEO metadata, body font family to `font`, and a dual favicon to one local variant |
|
|
59
|
+
| Content | Converts content `.md` to `.mdx`; preserves frontmatter, prose, fenced examples, MDX expressions, and local components; compiles every migrated MDX file |
|
|
60
|
+
| Components | Maps Note/Tip/Warning/Info/Check/Danger to Callout, ParamField/ResponseField to Field, Expandable to Accordion, CardGroup `cols` to `limit`, and Accordion `default-open` to `defaultOpen` |
|
|
61
|
+
| React adapters | Adds an editable `snippets/velu-mintlify-compat.jsx` for Tabs/Tab, Frame, Tooltip, Badge, Button, and Panel; imports only used adapters into each MDX page |
|
|
62
|
+
| Snippets | Preserves JSX/TSX modules; converts snippet-directory `.js` files to `.jsx` to avoid global execution; updates MDX and JS/TS module imports, including root-relative and extensionless imports |
|
|
63
|
+
| Custom JavaScript | Preserves browser `.js` files byte-for-byte and inventories their execution risks; Velu loads them after hydration, once per full page load, in path order |
|
|
64
|
+
| Custom CSS | Preserves styles; parses declarations to rewrite relative `url(...)` asset references to site-root paths; reports selectors/imports needing review |
|
|
65
|
+
| Assets | Copies files and mirrors ordinary images, fonts, media, downloads, CSS/JS, and explicitly linked data into `public/` for existing URLs; retains originals for branding and local imports |
|
|
66
|
+
| OpenAPI navigation | Preserves specs and references, reports missing/remote sources and the need to compare generated routes |
|
|
67
|
+
| OpenAPI MDX pages | Materializes `openapi: spec.json GET /path`, or `GET /path` with a root spec reference, into an editable API playground snapshot without changing the page path or removing its body; supports single local specs with internal references |
|
|
68
|
+
| npm scripts | Rewrites exact `mint`/`mintlify` dev, validate, version/v, and update invocations, including common package-manager launchers; adds the running Velu version as a dev dependency when Mintlify usage is found |
|
|
69
|
+
| Ignore rules | Honors `.mintignore` using gitignore semantics, including negation; excludes dependencies, build caches, environment files, and known tooling directories |
|
|
70
|
+
| Project tooling | Preserves shell scripts, package manifests, lockfiles, and CI workflows; flags Mintlify commands requiring manual replacement |
|
|
71
|
+
|
|
72
|
+
## Custom scripts and remaining work
|
|
73
|
+
|
|
74
|
+
Migration never executes project scripts, installs dependencies, or downloads
|
|
75
|
+
remote assets/specs. Run your package manager afterward when `package.json` has
|
|
76
|
+
changed; lockfiles and Mintlify dependencies are retained for review.
|
|
77
|
+
|
|
78
|
+
Mintlify and Velu share many DOM selectors, but arbitrary JavaScript cannot be
|
|
79
|
+
translated reliably without understanding its behavior. The report identifies
|
|
80
|
+
Mintlify browser APIs (`window.mintlify`, user events), Next.js internals,
|
|
81
|
+
DOMContentLoaded/load timing, module syntax in classic scripts, Node.js code in
|
|
82
|
+
globally loaded `.js` files, and DOM-dependent code. These checks are static
|
|
83
|
+
heuristics, not proof of complete runtime parity. For DOM-ready callbacks, check
|
|
84
|
+
`document.readyState` and initialize immediately when the event has already fired.
|
|
85
|
+
Verify behavior after client-side navigation as well as a full refresh.
|
|
86
|
+
|
|
87
|
+
The `css-dom` finding flags styles aimed at shared chrome IDs such as `#content`,
|
|
88
|
+
`#sidebar-content`, and `#footer`. Vepa aligns these common hooks with Sequoia,
|
|
89
|
+
including a content-body-only `#content` and route-specific body remounting, but
|
|
90
|
+
a shared ID does not guarantee identical nesting or dimensions for every component.
|
|
91
|
+
Selectors are preserved for review,
|
|
92
|
+
not automatically rewritten. Compare desktop and mobile pages with custom CSS
|
|
93
|
+
enabled; successful asset copying is not a visual parity score.
|
|
94
|
+
|
|
95
|
+
Endpoint snapshots are exported as `veluApi` metadata in MDX. Velu uses this
|
|
96
|
+
metadata for API layout, request/response samples, and sidebar method badges;
|
|
97
|
+
the original MDX body appears between the Try It bar and schema fields. Keep
|
|
98
|
+
the export when editing a migrated endpoint. Re-migrate after OpenAPI changes
|
|
99
|
+
to regenerate the snapshot.
|
|
100
|
+
|
|
101
|
+
Files under `public/` and dot directories are not automatically loaded by Velu;
|
|
102
|
+
their inclusion needs review. Root `.js` files are deliberately not rewritten
|
|
103
|
+
because blindly changing lifecycle logic, globals, or selectors can alter behavior.
|
|
104
|
+
Place Node tooling outside the docs content root or use `.cjs`/`.mjs` so it is not
|
|
105
|
+
loaded as a browser script.
|
|
106
|
+
|
|
107
|
+
The React adapters preserve basic interactions, not every Mintlify prop or exact
|
|
108
|
+
appearance. Panel renders inline, Tooltip uses a text-only native tooltip, and
|
|
109
|
+
Tabs supports keyboard navigation but does not synchronize selections across
|
|
110
|
+
separate tab groups. Unknown components are reported as errors instead of being
|
|
111
|
+
replaced with empty placeholders. Font Awesome/Tabler icon names may need Lucide
|
|
112
|
+
equivalents. Original references in code examples and prose remain unchanged.
|
|
113
|
+
|
|
114
|
+
Mintlify's `theme: "sequoia"` is converted to `theme: "vepa"` and activates the [Vepa preset](vepa.md).
|
|
115
|
+
The preset is not a certification of pixel equality for every component or custom script.
|
|
116
|
+
|
|
117
|
+
Features without full Velu equivalents require review: other Mintlify themes, advanced
|
|
118
|
+
fonts and styling, redirects, analytics/support integrations, hosting settings,
|
|
119
|
+
authentication, personalization, hidden-page routing, legacy version filtering,
|
|
120
|
+
complex dropdown/global navigation, AsyncAPI, manually authored `api:` pages,
|
|
121
|
+
multi-file/remote endpoint snapshots, OpenAPI overlays and `x-mint` extensions.
|
|
122
|
+
Access/visibility settings are reported as errors; they must be migrated before
|
|
123
|
+
publishing. Unlisted content is copied but Velu currently renders navigation
|
|
124
|
+
pages only. Snapshot API pages must be regenerated or edited when their specs
|
|
125
|
+
change. Existing generated robots/sitemap/LLM files can still contain old domains.
|
|
126
|
+
|
|
127
|
+
`mint broken-links`, `mint a11y`, `mint export`, cloud/account commands, compound
|
|
128
|
+
shell commands, and CLI flags have no automatically assumed equivalent. These
|
|
129
|
+
scripts and workflows are retained with findings. In particular, `velu validate`
|
|
130
|
+
is not a complete replacement for Mintlify's separate broken-link/a11y tools,
|
|
131
|
+
and the public Velu package does not expose the internal static build command.
|
|
132
|
+
|
|
133
|
+
## Parity research
|
|
134
|
+
|
|
135
|
+
Inspected on 2026-09-08:
|
|
136
|
+
|
|
137
|
+
- Published [`@mintlify/cli@4.0.1479`](https://www.npmjs.com/package/@mintlify/cli/v/4.0.1479): command dispatch/options in `bin/cli.js`; starter source in `bin/init.js`.
|
|
138
|
+
- Published [`@mintlify/common@1.0.1132`](https://www.npmjs.com/package/@mintlify/common/v/1.0.1132): content/snippet categories and `.mintignore` semantics in `dist/getFileCategory.js` and `dist/mintIgnore.js`.
|
|
139
|
+
- Published [`@mintlify/prebuild@1.0.1286`](https://www.npmjs.com/package/@mintlify/prebuild/v/1.0.1286): split config references and config loading.
|
|
140
|
+
- [`docs.json` schema](https://www.mintlify.com/docs.json), [custom scripts documentation](https://www.mintlify.com/docs/customize/custom-scripts), and [official starter](https://github.com/mintlify/starter).
|
|
141
|
+
|
|
142
|
+
The current `@mintlify/validation@0.1.849` tarball was also retrieved, but it did
|
|
143
|
+
not contain its declared `dist/` implementation. Legacy conversion is implemented
|
|
144
|
+
locally; it does not depend on executing Mintlify's upgrade code. No Mintlify
|
|
145
|
+
implementation code or package is vendored as a runtime dependency.
|
|
146
|
+
|
|
147
|
+
## Verification
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
node --test packages/velu-cli/test/migrate.test.mjs
|
|
151
|
+
node --test packages/velu-cli/test/*.test.mjs
|
|
152
|
+
node packages/velu-cli/scripts/build.mjs
|
|
153
|
+
node packages/velu-cli/dist/cli.js migrate --help
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Tests cover clean and review-required outputs, source immutability, JSONC/YAML
|
|
157
|
+
references, ignore negation, collisions/junctions, script preservation, AST
|
|
158
|
+
rewrites, assets, endpoint snapshots, generated-project validation, and CLI exit
|
|
159
|
+
codes. No network is needed for the migration tests.
|
package/docs/vepa.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Vepa (வேப்பம்) compatibility
|
|
2
|
+
|
|
3
|
+
Enable the preset in `velu.json`:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"name": "Documentation",
|
|
8
|
+
"theme": "vepa",
|
|
9
|
+
"colors": { "primary": "#16A34A", "light": "#07C983" },
|
|
10
|
+
"navigation": { "groups": [{ "group": "Guide", "pages": ["index"] }] }
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Mintlify migration converts `theme: "sequoia"` to `theme: "vepa"`. The normal Velu appearance is
|
|
15
|
+
still the default when `theme` is omitted. The visual preset is separate from
|
|
16
|
+
the reader's Light / Dark / System preference.
|
|
17
|
+
|
|
18
|
+
For Vepa, `colors.primary` is the light-mode accent and `colors.light` is the
|
|
19
|
+
dark-mode accent, matching Mintlify. The primary color also tints the gray ramp.
|
|
20
|
+
`font.family` overrides Inter. Development, prerendered builds, and the browser
|
|
21
|
+
preview share these normalizers.
|
|
22
|
+
|
|
23
|
+
## Reference inspected
|
|
24
|
+
|
|
25
|
+
The npm CLI does not ship the renderer inside its own package. Its previewing
|
|
26
|
+
dependency retrieves a separately versioned client from Mintlify's release host.
|
|
27
|
+
Research was pinned on 2026-09-08 to:
|
|
28
|
+
|
|
29
|
+
- [`@mintlify/cli@4.0.1479`](https://www.npmjs.com/package/@mintlify/cli/v/4.0.1479)
|
|
30
|
+
- [`@mintlify/previewing@4.0.1358`](https://www.npmjs.com/package/@mintlify/previewing/v/4.0.1358), `dist/constants.js`, `dist/util.js`, and `dist/local-preview/client.js`
|
|
31
|
+
- [Client 0.0.3566](https://releases.mintlify.com/mint-0.0.3566.tar.gz), compiled Sequoia topbar, sidebar, page-header, and TOC modules
|
|
32
|
+
- [`@mintlify/common@1.0.1132`](https://www.npmjs.com/package/@mintlify/common/v/1.0.1132), color composition
|
|
33
|
+
|
|
34
|
+
Client archive SHA-256:
|
|
35
|
+
`a2b758fd67159a066e14fa7e78599047b0fd7f5ae78477e7364efdc232ef7ee5`
|
|
36
|
+
|
|
37
|
+
The downloaded client is research material only. It is not included in Velu's
|
|
38
|
+
published package or used at runtime.
|
|
39
|
+
|
|
40
|
+
## Measured geometry
|
|
41
|
+
|
|
42
|
+
| Region | Vepa preset |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Desktop topbar | 48 px; navigation tabs add 48 px |
|
|
45
|
+
| Logo | 22 px high |
|
|
46
|
+
| Desktop search trigger | 256 × 32 px |
|
|
47
|
+
| Left sidebar | 288 px |
|
|
48
|
+
| Default article at 1440 px viewport | 648 px; left edge 396 px |
|
|
49
|
+
| Table of contents | 288 px rail with 32 px side padding |
|
|
50
|
+
| API examples | 472 px rail including a 24 px outer gutter; 448 px sample cards |
|
|
51
|
+
| API rail breakpoint | Hidden below 1280 px; article reclaims its space |
|
|
52
|
+
| On-page TOC bar | Sticky TocBar below 1280 px (when the right rail hides), same as default Velu |
|
|
53
|
+
| Mobile navigation | Below 640 px; crumbs + burger (tabs stay on tablet with horizontal scroll) |
|
|
54
|
+
| Title | Inter, 500 weight, 36/40 px; 30/36 px below 640 px |
|
|
55
|
+
|
|
56
|
+
The API rail deliberately has its own width and padding. Applying the normal
|
|
57
|
+
TOC's width leaves too little room for request tabs and response examples. Long
|
|
58
|
+
code lines scroll inside their code panel instead of widening the page.
|
|
59
|
+
|
|
60
|
+
The preset includes a plain TOC, tinted light/dark surfaces, a three-way theme
|
|
61
|
+
menu, responsive columns, and mode-specific content widths. Card links retain
|
|
62
|
+
their destinations, and navigation uses `sidebarTitle` when supplied.
|
|
63
|
+
|
|
64
|
+
For custom styles, Vepa exposes `#page-title` on the title, `#content` on the
|
|
65
|
+
ordinary MDX body (excluding the header), and `#sidebar-content` on the sidebar.
|
|
66
|
+
The MDX body remounts on route changes so DOM decorations do not leak into the
|
|
67
|
+
next page. Body font overrides inherit into prose; component headings retain
|
|
68
|
+
their own typography. API pages use their own content structure.
|
|
69
|
+
|
|
70
|
+
Migrated endpoint MDX exports a `veluApi` snapshot containing the operation,
|
|
71
|
+
samples, and API settings. Both runtimes use it to activate the API layout and
|
|
72
|
+
navigation method badges. Author imports and exports stay at module scope;
|
|
73
|
+
custom content renders after the Try It bar and before the schema fields.
|
|
74
|
+
|
|
75
|
+
## Verification and limits
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
node --test packages/velu-cli/test/vepa.test.mjs
|
|
79
|
+
node packages/velu-cli/scripts/check-vepa-layout.mjs http://localhost:8358 /api-reference/products/listproducts /api-reference/products/addproduct
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The browser check covers GET/POST pages at 1440, 1280, 1100, 768, and 390 px in
|
|
83
|
+
light/dark mode. It checks rail size, sample width, column overlap, document
|
|
84
|
+
overflow, closed drawers, language tabs, system preference changes, and runtime
|
|
85
|
+
errors. Supply routes from your own running site.
|
|
86
|
+
|
|
87
|
+
This is a source-informed compatibility preset, **not yet verified pixel-for-pixel
|
|
88
|
+
across Mintlify's complete component/state matrix**. Velu still uses its own
|
|
89
|
+
search, API playground, syntax highlighting, icons, feedback, and content
|
|
90
|
+
components. Font availability can also affect screenshots. Hosted Mintlify
|
|
91
|
+
services are not reproduced by copying theme settings. Custom scripts and CSS
|
|
92
|
+
that depend on Mintlify's exact DOM, globals, lifecycle, or unsupported components
|
|
93
|
+
need testing on the migrated site.
|
|
94
|
+
|
|
95
|
+
Below the API rail breakpoint, samples move inline after the Try It bar and
|
|
96
|
+
before custom content. Inline and desktop samples use distinct IDs. This
|
|
97
|
+
document records the current scope rather than asserting complete parity.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veluai/velu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": "./dist/cli.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"src/lib/known-components.js",
|
|
17
17
|
"runtime/velu-ui/**",
|
|
18
18
|
"schema/**",
|
|
19
|
-
"templates/**"
|
|
19
|
+
"templates/**",
|
|
20
|
+
"docs/mintlify-migration.md",
|
|
21
|
+
"docs/vepa.md"
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
22
24
|
"build": "node scripts/build.mjs",
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
28
30
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
31
|
+
"@babel/parser": "7.28.0",
|
|
29
32
|
"@icons-pack/react-simple-icons": ">=13.0.0 <13.13.0",
|
|
30
33
|
"@mdx-js/mdx": "^3.1.1",
|
|
31
34
|
"@mdx-js/react": "^3.1.1",
|
|
@@ -36,10 +39,13 @@
|
|
|
36
39
|
"estree-util-value-to-estree": "^3.5.0",
|
|
37
40
|
"express": "^4.21.1",
|
|
38
41
|
"github-slugger": "^2.0.0",
|
|
42
|
+
"ignore": "7.0.5",
|
|
39
43
|
"iso-639-1": "^3.1.5",
|
|
40
44
|
"js-yaml": "^4.1.0",
|
|
45
|
+
"jsonc-parser": "3.3.1",
|
|
41
46
|
"lucide-react": "^0.460.0",
|
|
42
47
|
"mdast-util-to-string": "^4.0.0",
|
|
48
|
+
"postcss": "8.5.0",
|
|
43
49
|
"prism-react-renderer": "^2.4.0",
|
|
44
50
|
"prismjs": "^1.29.0",
|
|
45
51
|
"react": "^18.3.1",
|
|
@@ -27,7 +27,7 @@ import { sendApiRequest } from '../lib/api-send.js';
|
|
|
27
27
|
*/
|
|
28
28
|
const LOCATION_LABEL = { path: 'Path Parameters', query: 'Query Parameters', header: 'Header Parameters' };
|
|
29
29
|
|
|
30
|
-
export default function ApiReferencePage({ operation, samples = [], apiOperations = [], api = {} }) {
|
|
30
|
+
export default function ApiReferencePage({ operation, samples = [], apiOperations = [], api = {}, children, preset }) {
|
|
31
31
|
const [open, setOpen] = useState(false);
|
|
32
32
|
// Which operation the playground is currently showing. Defaults to this
|
|
33
33
|
// page's own operation; the dropdown can switch it to any sibling.
|
|
@@ -78,6 +78,9 @@ export default function ApiReferencePage({ operation, samples = [], apiOperation
|
|
|
78
78
|
className="velu-api-page__tryit"
|
|
79
79
|
/>
|
|
80
80
|
|
|
81
|
+
{preset === 'vepa' && <div className="velu-api-page__inline-samples"><ApiSamples idPrefix="inline-" samples={samples} responses={operation.responses} preset={preset} title={operation.title} /></div>}
|
|
82
|
+
{children && <div className="velu-api-page__custom-content">{children}</div>}
|
|
83
|
+
|
|
81
84
|
{/* Authorizations */}
|
|
82
85
|
{operation.auth?.length > 0 && (
|
|
83
86
|
<api-section class="velu-api-page__section">
|
|
@@ -124,7 +127,7 @@ export default function ApiReferencePage({ operation, samples = [], apiOperation
|
|
|
124
127
|
{operation.responses?.length > 0 && (
|
|
125
128
|
<api-section class="velu-api-page__section">
|
|
126
129
|
<h2 data-component="api-section-heading">Response</h2>
|
|
127
|
-
<ResponseSections responses={operation.responses} />
|
|
130
|
+
<ResponseSections responses={operation.responses} preset={preset} />
|
|
128
131
|
</api-section>
|
|
129
132
|
)}
|
|
130
133
|
|
|
@@ -175,7 +178,12 @@ function EnumHint({ values }) {
|
|
|
175
178
|
);
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
function ResponseSections({ responses }) {
|
|
181
|
+
function ResponseSections({ responses, preset }) {
|
|
182
|
+
if (preset === 'vepa') return <div className="velu-vepa-responses">{responses.map((r, i) => <section key={i}>
|
|
183
|
+
<div className="velu-vepa-responses__status">{r.status}{r.contentType && ` · ${r.contentType}`}</div>
|
|
184
|
+
{r.description && <p>{r.description}</p>}
|
|
185
|
+
{r.fields?.map((f, j) => <Field key={j} name={f.name} type={f.type} required={f.required}>{f.description}</Field>)}
|
|
186
|
+
</section>)}</div>;
|
|
179
187
|
return (
|
|
180
188
|
<AccordionGroup className="velu-api-resp">
|
|
181
189
|
{responses.map((r, i) => {
|
|
@@ -9,12 +9,17 @@ import CodeBlock, { CodeGroup } from './CodeBlock.jsx';
|
|
|
9
9
|
*
|
|
10
10
|
* @param {{ samples: {key,label,language,code}[], responses: object[] }} props
|
|
11
11
|
*/
|
|
12
|
-
export default function ApiSamples({ samples = [], responses = [] }) {
|
|
12
|
+
export default function ApiSamples({ samples = [], responses = [], preset, title, idPrefix = '' }) {
|
|
13
|
+
const [selected, setSelected] = React.useState('');
|
|
14
|
+
const sample = samples.find((s) => s.key === selected) || samples[0];
|
|
13
15
|
const withExample = responses.filter((r) => r.example != null);
|
|
14
16
|
return (
|
|
15
17
|
<div className="velu-api-samples">
|
|
16
|
-
{
|
|
17
|
-
<
|
|
18
|
+
{sample && preset === 'vepa' ? <section id={idPrefix + 'request-example'} className="velu-vepa-request">
|
|
19
|
+
<div className="velu-vepa-request__header"><span>{title}</span><select aria-label="Request example language" value={sample.key} onChange={(e) => setSelected(e.target.value)}>{samples.map((s) => <option key={s.key} value={s.key}>{s.label}</option>)}</select></div>
|
|
20
|
+
<CodeBlock language={sample.language}>{sample.code}</CodeBlock>
|
|
21
|
+
</section> : samples.length > 0 && (
|
|
22
|
+
<CodeGroup id={idPrefix + 'request-example'} className="velu-api-samples__req">
|
|
18
23
|
{samples.map((s) => (
|
|
19
24
|
<CodeBlock key={s.key} title={s.label} language={s.language}>
|
|
20
25
|
{s.code}
|
|
@@ -23,7 +28,7 @@ export default function ApiSamples({ samples = [], responses = [] }) {
|
|
|
23
28
|
</CodeGroup>
|
|
24
29
|
)}
|
|
25
30
|
{withExample.length > 0 && (
|
|
26
|
-
<CodeGroup id=
|
|
31
|
+
<CodeGroup id={idPrefix + 'response-example'} className="velu-api-samples__res">
|
|
27
32
|
{withExample.map((r) => (
|
|
28
33
|
<CodeBlock key={r.status} title={r.status} language="json">
|
|
29
34
|
{JSON.stringify(r.example, null, 2)}
|
|
@@ -5,9 +5,9 @@ import Switcher from '../primitives/Switcher.jsx';
|
|
|
5
5
|
/**
|
|
6
6
|
* Card + CardGroup — content / horizontal / image / CTA cards.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* single "Click Here ›"
|
|
8
|
+
* Pass `href` (without `cta`) to make the whole card a stretched link.
|
|
9
|
+
* Nested links/buttons inside the card stay clickable above that overlay.
|
|
10
|
+
* Pass `cta={{ label, href }}` for a single "Click Here ›" control instead.
|
|
11
11
|
*
|
|
12
12
|
* Fully tokenized (border-width, border-color, radius scale, spacing, type,
|
|
13
13
|
* line-height, accent, icon-stroke) → light/dark automatic via [data-theme].
|
|
@@ -22,7 +22,7 @@ import Switcher from '../primitives/Switcher.jsx';
|
|
|
22
22
|
* `icon` is a lucide id string (shared resolveIcon).
|
|
23
23
|
*
|
|
24
24
|
* @param {{ title?: React.ReactNode, icon?: string, horizontal?: boolean,
|
|
25
|
-
* img?: string, imgAlt?: string,
|
|
25
|
+
* img?: string, imgAlt?: string, href?: string,
|
|
26
26
|
* cta?: {label: React.ReactNode, href: string},
|
|
27
27
|
* children?: React.ReactNode, className?: string }} props
|
|
28
28
|
*/
|
|
@@ -34,6 +34,7 @@ export function Card({
|
|
|
34
34
|
img,
|
|
35
35
|
imgAlt = '',
|
|
36
36
|
cta,
|
|
37
|
+
href,
|
|
37
38
|
children,
|
|
38
39
|
className = '',
|
|
39
40
|
...rest
|
|
@@ -72,6 +73,7 @@ export function Card({
|
|
|
72
73
|
|
|
73
74
|
return (
|
|
74
75
|
<card className={cls} {...rest}>
|
|
76
|
+
{href && !cta && <a className="velu-card__link" href={href} aria-label={typeof title === 'string' ? title : 'Read more'} />}
|
|
75
77
|
{img && (
|
|
76
78
|
<div className="velu-card__media" data-component-part="card-image">
|
|
77
79
|
<img src={img} alt={imgAlt} loading="lazy" />
|
|
@@ -122,6 +124,7 @@ export function CardGroup({
|
|
|
122
124
|
space={space}
|
|
123
125
|
min={min}
|
|
124
126
|
limit={limit}
|
|
127
|
+
style={{ '--vepa-columns': Math.max(1, Math.min(4, Number(limit) || 2)) }}
|
|
125
128
|
{...rest}
|
|
126
129
|
>
|
|
127
130
|
{children}
|
|
@@ -336,7 +336,7 @@ function renderStream(tokens, sources, onNavigate) {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
/* ── Header ─────────────────────────────────────────────────────────── */
|
|
339
|
-
function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
|
|
339
|
+
function ChatHeader({ onClose, onNew, onHistory, historyOpen, showClose = true }) {
|
|
340
340
|
return (
|
|
341
341
|
<Cluster
|
|
342
342
|
as="chat-assistant-sheet-header"
|
|
@@ -370,6 +370,7 @@ function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
|
|
|
370
370
|
>
|
|
371
371
|
{resolveIcon('plus', { size: '1em' })}
|
|
372
372
|
</button>
|
|
373
|
+
{showClose && (
|
|
373
374
|
<button
|
|
374
375
|
type="button"
|
|
375
376
|
className="velu-chatbot__iconbtn"
|
|
@@ -378,6 +379,7 @@ function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
|
|
|
378
379
|
>
|
|
379
380
|
{resolveIcon('x', { size: '1em' })}
|
|
380
381
|
</button>
|
|
382
|
+
)}
|
|
381
383
|
</Cluster>
|
|
382
384
|
</Cluster>
|
|
383
385
|
);
|
|
@@ -655,6 +657,7 @@ export default function Chatbot({
|
|
|
655
657
|
listHistory,
|
|
656
658
|
loadConversation,
|
|
657
659
|
suggestions,
|
|
660
|
+
layout = 'panel',
|
|
658
661
|
className = '',
|
|
659
662
|
...rest
|
|
660
663
|
}) {
|
|
@@ -882,8 +885,9 @@ export default function Chatbot({
|
|
|
882
885
|
const isBottomSheet = () =>
|
|
883
886
|
typeof window !== 'undefined' &&
|
|
884
887
|
window.matchMedia('(max-width: 1024px)').matches;
|
|
888
|
+
const isPage = layout === 'page';
|
|
885
889
|
const onDragStart = (e) => {
|
|
886
|
-
if (!isBottomSheet()) return;
|
|
890
|
+
if (isPage || !isBottomSheet()) return;
|
|
887
891
|
dragRef.current = { active: true, startY: e.clientY, currentY: 0 };
|
|
888
892
|
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
889
893
|
};
|
|
@@ -906,7 +910,7 @@ export default function Chatbot({
|
|
|
906
910
|
if (!open) setDragOffset(0);
|
|
907
911
|
}, [open]);
|
|
908
912
|
|
|
909
|
-
const cls = `velu-chatbot${open ? ' velu-chatbot--open' : ''} ${className}`.trim();
|
|
913
|
+
const cls = `velu-chatbot${open || isPage ? ' velu-chatbot--open' : ''}${isPage ? ' velu-chatbot--page' : ''} ${className}`.trim();
|
|
910
914
|
const dragStyle =
|
|
911
915
|
dragOffset > 0
|
|
912
916
|
? { transform: `translateY(${dragOffset}px)`, transition: 'none' }
|
|
@@ -917,7 +921,7 @@ export default function Chatbot({
|
|
|
917
921
|
id="chat-assistant-sheet"
|
|
918
922
|
data-component="chat-assistant-sheet"
|
|
919
923
|
className={cls}
|
|
920
|
-
aria-hidden={!open}
|
|
924
|
+
aria-hidden={isPage ? false : !open}
|
|
921
925
|
aria-label="Ask AI"
|
|
922
926
|
style={dragStyle}
|
|
923
927
|
{...rest}
|
|
@@ -939,6 +943,7 @@ export default function Chatbot({
|
|
|
939
943
|
onNew={newChat}
|
|
940
944
|
onHistory={() => setHistoryOpen((o) => !o)}
|
|
941
945
|
historyOpen={historyOpen}
|
|
946
|
+
showClose={!isPage}
|
|
942
947
|
/>
|
|
943
948
|
|
|
944
949
|
{historyOpen && (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import VepaFooter from './VepaFooter.jsx';
|
|
2
3
|
import Stack from '../primitives/Stack.jsx';
|
|
3
4
|
import Cluster from '../primitives/Cluster.jsx';
|
|
4
5
|
import SocialLinks from './SocialLinks.jsx';
|
|
@@ -67,6 +68,7 @@ function VeluMark() {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
export default function PageFooter({
|
|
71
|
+
preset,
|
|
70
72
|
brand,
|
|
71
73
|
columns = [],
|
|
72
74
|
socials = [],
|
|
@@ -75,6 +77,7 @@ export default function PageFooter({
|
|
|
75
77
|
...rest
|
|
76
78
|
}) {
|
|
77
79
|
const Link = linkComponent;
|
|
80
|
+
if (preset === 'vepa') return <VepaFooter brand={brand} columns={columns} socials={socials} linkComponent={Link} />;
|
|
78
81
|
const brandLabel = brand?.label ?? 'Velu';
|
|
79
82
|
const brandHref = brand?.href;
|
|
80
83
|
|
|
@@ -74,7 +74,7 @@ function ActionButton({ action, Link, id }) {
|
|
|
74
74
|
// Mintlify-compatible hooks: `#topbar-cta-button` on the first primary
|
|
75
75
|
// action, `[data-component="navbar-link"]` on every action (overridable per
|
|
76
76
|
// action, e.g. component: 'logout-link').
|
|
77
|
-
const hooks = { id, 'data-component': component ?? 'navbar-link' };
|
|
77
|
+
const hooks = { id, 'data-component': component ?? 'navbar-link', 'data-component-name': kind === 'primary' ? 'primary-header-button' : undefined };
|
|
78
78
|
const content = (
|
|
79
79
|
<>
|
|
80
80
|
{icon && (
|
|
@@ -232,6 +232,7 @@ function KebabMenu({ actions, Link }) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
export default function PageHeader({
|
|
235
|
+
preset,
|
|
235
236
|
brand,
|
|
236
237
|
brandTrailing,
|
|
237
238
|
center,
|
|
@@ -266,6 +267,12 @@ export default function PageHeader({
|
|
|
266
267
|
return () => window.removeEventListener('scroll', onScroll);
|
|
267
268
|
}, []);
|
|
268
269
|
|
|
270
|
+
const showTabsRow =
|
|
271
|
+
tabs.length > 0 ||
|
|
272
|
+
(breadcrumb && breadcrumb.length > 0) ||
|
|
273
|
+
Boolean(tabsTrailing) ||
|
|
274
|
+
Boolean(onMenuClick);
|
|
275
|
+
|
|
269
276
|
// A configured logo replaces the mark + wordmark entirely (Mintlify-style).
|
|
270
277
|
const brandBlock = (
|
|
271
278
|
<Cluster space="var(--s-3)" align="center" className="velu-header__brand" style={{ flexWrap: 'nowrap' }}>
|
|
@@ -283,8 +290,11 @@ export default function PageHeader({
|
|
|
283
290
|
return (
|
|
284
291
|
<Stack
|
|
285
292
|
as="header"
|
|
286
|
-
space=
|
|
287
|
-
|
|
293
|
+
space={preset === 'vepa' ? '0px' : 'var(--s0)'}
|
|
294
|
+
data-has-tabs={tabs.length > 0 ? 'true' : 'false'}
|
|
295
|
+
className={`velu-header${scrolled ? ' velu-header--scrolled' : ''}${
|
|
296
|
+
showTabsRow ? '' : ' velu-header--no-tabs'
|
|
297
|
+
} ${className}`.trim()}
|
|
288
298
|
{...rest}
|
|
289
299
|
>
|
|
290
300
|
{/* Top row — 3-column grid: brand | center | actions. */}
|
|
@@ -349,10 +359,7 @@ export default function PageHeader({
|
|
|
349
359
|
takes their place. Burger stays at the trailing edge via
|
|
350
360
|
margin-inline-start: auto on the burger button itself, so
|
|
351
361
|
its position is unaffected by the breadcrumb's length. */}
|
|
352
|
-
{
|
|
353
|
-
(breadcrumb && breadcrumb.length > 0) ||
|
|
354
|
-
tabsTrailing ||
|
|
355
|
-
onMenuClick) && (
|
|
362
|
+
{showTabsRow && (
|
|
356
363
|
<Cluster
|
|
357
364
|
as="nav-tabs"
|
|
358
365
|
space="var(--s0)"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Sun, Moon, Monitor, Check } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export default function ThemePreferenceMenu({ inline = false }) {
|
|
5
|
+
const [open, setOpen] = React.useState(false);
|
|
6
|
+
const [preference, setPreference] = React.useState('system');
|
|
7
|
+
const root = React.useRef(null);
|
|
8
|
+
const trigger = React.useRef(null);
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
11
|
+
const sync = () => {
|
|
12
|
+
let pref = 'system';
|
|
13
|
+
try { pref = localStorage.getItem('velu-theme') || 'system'; } catch {}
|
|
14
|
+
setPreference(pref);
|
|
15
|
+
document.documentElement.dataset.theme = pref === 'system' ? (media.matches ? 'dark' : 'light') : pref;
|
|
16
|
+
};
|
|
17
|
+
sync();
|
|
18
|
+
media.addEventListener('change', sync);
|
|
19
|
+
window.addEventListener('storage', sync);
|
|
20
|
+
window.addEventListener('velu-theme-change', sync);
|
|
21
|
+
return () => { media.removeEventListener('change', sync); window.removeEventListener('storage', sync); window.removeEventListener('velu-theme-change', sync); };
|
|
22
|
+
}, []);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (!open) return;
|
|
25
|
+
root.current?.querySelector('[aria-checked="true"]')?.focus();
|
|
26
|
+
const outside = (e) => { if (!root.current?.contains(e.target)) setOpen(false); };
|
|
27
|
+
document.addEventListener('pointerdown', outside);
|
|
28
|
+
return () => document.removeEventListener('pointerdown', outside);
|
|
29
|
+
}, [open]);
|
|
30
|
+
const choose = (value) => {
|
|
31
|
+
setPreference(value);
|
|
32
|
+
try { value === 'system' ? localStorage.removeItem('velu-theme') : localStorage.setItem('velu-theme', value); } catch {}
|
|
33
|
+
document.documentElement.dataset.theme = value === 'system' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : value;
|
|
34
|
+
window.dispatchEvent(new Event('velu-theme-change'));
|
|
35
|
+
setOpen(false);
|
|
36
|
+
trigger.current?.focus();
|
|
37
|
+
};
|
|
38
|
+
const Icon = preference === 'system' ? Monitor : preference === 'dark' ? Moon : Sun;
|
|
39
|
+
if (inline) return <div className="vepa-footer-theme" role="group" aria-label="Color theme">
|
|
40
|
+
{[[Monitor, 'system', 'System'], [Sun, 'light', 'Light'], [Moon, 'dark', 'Dark']].map(([Glyph, value, label]) => <button key={value} type="button" aria-label={`${label} theme`} aria-pressed={preference === value} onClick={() => choose(value)}><Glyph size={16} aria-hidden="true" /></button>)}
|
|
41
|
+
</div>;
|
|
42
|
+
return <div ref={root} className="velu-theme-menu" onKeyDown={(e) => {
|
|
43
|
+
if (e.key === 'Escape') { setOpen(false); trigger.current?.focus(); }
|
|
44
|
+
if (e.key === 'Tab') setOpen(false);
|
|
45
|
+
if (open && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const items = [...root.current.querySelectorAll('[role="menuitemradio"]')];
|
|
48
|
+
const i = items.indexOf(document.activeElement);
|
|
49
|
+
items[e.key === 'Home' ? 0 : e.key === 'End' ? 2 : (i + (e.key === 'ArrowUp' ? 2 : 1)) % 3]?.focus();
|
|
50
|
+
}
|
|
51
|
+
}}>
|
|
52
|
+
<button ref={trigger} type="button" className="velu-theme-menu__trigger" aria-label="Theme preference" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen(!open)}><Icon size={16} aria-hidden="true" /></button>
|
|
53
|
+
{open && <div className="velu-theme-menu__options" role="menu" aria-label="Theme preference">
|
|
54
|
+
{[[Sun, 'light', 'Light'], [Moon, 'dark', 'Dark'], [Monitor, 'system', 'System']].map(([Glyph, value, label]) => <button key={value} type="button" role="menuitemradio" aria-checked={preference === value} tabIndex={-1} onClick={() => choose(value)}><Glyph size={16} aria-hidden="true" /><span>{label}</span>{preference === value && <Check size={14} aria-hidden="true" />}</button>)}
|
|
55
|
+
</div>}
|
|
56
|
+
</div>;
|
|
57
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Sun, Moon } from 'lucide-react';
|
|
3
|
+
import ThemePreferenceMenu from './ThemePreferenceMenu.jsx';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* ThemeToggle — pill-shaped two-state light/dark switch. The thumb
|
|
@@ -14,7 +15,8 @@ import { Sun, Moon } from 'lucide-react';
|
|
|
14
15
|
* explicit choice (which then overrides the OS preference on future
|
|
15
16
|
* visits, per the anti-flash script in the template).
|
|
16
17
|
*/
|
|
17
|
-
export default function ThemeToggle({ className = '', ...rest }) {
|
|
18
|
+
export default function ThemeToggle({ className = '', variant, ...rest }) {
|
|
19
|
+
if (variant === 'menu') return <ThemePreferenceMenu />;
|
|
18
20
|
function toggle() {
|
|
19
21
|
const root = document.documentElement;
|
|
20
22
|
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
|