@veluai/velu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +11 -0
- package/package.json +52 -0
- package/runtime/velu-ui/base.css +311 -0
- package/runtime/velu-ui/components/Accordion.jsx +64 -0
- package/runtime/velu-ui/components/ApiClient.jsx +121 -0
- package/runtime/velu-ui/components/ApiField.jsx +87 -0
- package/runtime/velu-ui/components/ApiPath.jsx +63 -0
- package/runtime/velu-ui/components/ApiSidebar.jsx +122 -0
- package/runtime/velu-ui/components/AskBar.jsx +71 -0
- package/runtime/velu-ui/components/Callout.jsx +114 -0
- package/runtime/velu-ui/components/Card.jsx +131 -0
- package/runtime/velu-ui/components/Chatbot.jsx +596 -0
- package/runtime/velu-ui/components/CodeBlock.jsx +375 -0
- package/runtime/velu-ui/components/Columns.jsx +56 -0
- package/runtime/velu-ui/components/Field.jsx +81 -0
- package/runtime/velu-ui/components/Image.jsx +163 -0
- package/runtime/velu-ui/components/MethodBadge.jsx +31 -0
- package/runtime/velu-ui/components/NavSelect.jsx +108 -0
- package/runtime/velu-ui/components/PageFeedback.jsx +219 -0
- package/runtime/velu-ui/components/PageFooter.jsx +213 -0
- package/runtime/velu-ui/components/PageHeader.jsx +414 -0
- package/runtime/velu-ui/components/PageNav.jsx +77 -0
- package/runtime/velu-ui/components/PoweredBy.jsx +51 -0
- package/runtime/velu-ui/components/Prompt.jsx +115 -0
- package/runtime/velu-ui/components/Search.jsx +366 -0
- package/runtime/velu-ui/components/Sidebar.jsx +191 -0
- package/runtime/velu-ui/components/Steps.jsx +65 -0
- package/runtime/velu-ui/components/ThemeToggle.jsx +48 -0
- package/runtime/velu-ui/components/Toc.jsx +537 -0
- package/runtime/velu-ui/components/TocBar.jsx +195 -0
- package/runtime/velu-ui/components/Tree.jsx +87 -0
- package/runtime/velu-ui/components/TryItBar.jsx +90 -0
- package/runtime/velu-ui/components/accordion.css +92 -0
- package/runtime/velu-ui/components/api.css +479 -0
- package/runtime/velu-ui/components/ask-bar.css +94 -0
- package/runtime/velu-ui/components/card.css +105 -0
- package/runtime/velu-ui/components/chatbot.css +617 -0
- package/runtime/velu-ui/components/code-block.css +263 -0
- package/runtime/velu-ui/components/docs-layout.css +775 -0
- package/runtime/velu-ui/components/field.css +82 -0
- package/runtime/velu-ui/components/image.css +237 -0
- package/runtime/velu-ui/components/nav-select.css +157 -0
- package/runtime/velu-ui/components/page-feedback.css +241 -0
- package/runtime/velu-ui/components/page-footer.css +130 -0
- package/runtime/velu-ui/components/page-header.css +520 -0
- package/runtime/velu-ui/components/page-nav.css +50 -0
- package/runtime/velu-ui/components/powered-by.css +66 -0
- package/runtime/velu-ui/components/prompt.css +99 -0
- package/runtime/velu-ui/components/search.css +307 -0
- package/runtime/velu-ui/components/sidebar.css +144 -0
- package/runtime/velu-ui/components/steps.css +77 -0
- package/runtime/velu-ui/components/theme-toggle.css +70 -0
- package/runtime/velu-ui/components/toc-bar.css +234 -0
- package/runtime/velu-ui/components/tree.css +49 -0
- package/runtime/velu-ui/index.js +45 -0
- package/runtime/velu-ui/lib/copyText.js +64 -0
- package/runtime/velu-ui/lib/lang-icons.jsx +156 -0
- package/runtime/velu-ui/lib/prism-langs.js +957 -0
- package/runtime/velu-ui/lib/prism-loader.js +74 -0
- package/runtime/velu-ui/lib/resolveIcon.jsx +29 -0
- package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -0
- package/runtime/velu-ui/mdx-components.jsx +85 -0
- package/runtime/velu-ui/primitives/Cluster.jsx +49 -0
- package/runtime/velu-ui/primitives/Stack.jsx +63 -0
- package/runtime/velu-ui/primitives/Switcher.jsx +57 -0
- package/runtime/velu-ui/primitives/stack.css +3 -0
- package/runtime/velu-ui/primitives/switcher.css +25 -0
- package/runtime/velu-ui/styles.css +43 -0
- package/runtime/velu-ui/tokens.css +4 -0
- package/schema/velu.schema.json +167 -0
- package/src/navigation.js +434 -0
- package/src/runtime/App.jsx +1473 -0
- package/src/runtime/client-entry.jsx +22 -0
- package/src/runtime/server-entry.jsx +16 -0
- package/src/template.html +48 -0
- package/templates/starter/ai-tools/claude-code.mdx +26 -0
- package/templates/starter/ai-tools/cursor.mdx +17 -0
- package/templates/starter/api-reference/endpoint/create.mdx +24 -0
- package/templates/starter/api-reference/endpoint/get.mdx +27 -0
- package/templates/starter/api-reference/introduction.mdx +28 -0
- package/templates/starter/development.mdx +19 -0
- package/templates/starter/essentials/code.mdx +28 -0
- package/templates/starter/essentials/images.mdx +29 -0
- package/templates/starter/essentials/markdown.mdx +25 -0
- package/templates/starter/essentials/navigation.mdx +39 -0
- package/templates/starter/essentials/settings.mdx +30 -0
- package/templates/starter/favicon.svg +6 -0
- package/templates/starter/index.mdx +31 -0
- package/templates/starter/quickstart.mdx +31 -0
- package/templates/starter/velu.json +33 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var me=Object.defineProperty;var g=(e,t)=>()=>(e&&(t=e(e=0)),t);var j=(e,t)=>{for(var o in t)me(e,o,{get:t[o],enumerable:!0})};var R={};j(R,{initProject:()=>ye});import y from"node:fs/promises";import w from"node:path";import{fileURLToPath as ge}from"node:url";async function ye(e,t){try{if((await y.readdir(e)).length>0)throw new Error(`Directory "${t}" already exists and is not empty. Choose a different name or remove the folder first.`)}catch(i){if(i.code!=="ENOENT")throw i}await y.mkdir(e,{recursive:!0}),await y.cp(ve,e,{recursive:!0});let o=w.join(e,"velu.json"),n=JSON.parse(await y.readFile(o,"utf-8"));n.name=t,await y.writeFile(o,JSON.stringify(n,null,2)+`
|
|
3
|
+
`,"utf-8");let r=await L(e);console.log(`[velu] created ${t}/`);for(let i of r)console.log(` ${i}`);console.log(""),console.log("Next steps:"),console.log(` cd ${t}`),console.log(" velu dev")}async function L(e,t=e,o=[]){let n=await y.readdir(e,{withFileTypes:!0});for(let r of n){let i=w.join(e,r.name);r.isDirectory()?await L(i,t,o):o.push(w.relative(t,i).split(w.sep).join("/"))}return o.sort()}var he,ve,_=g(()=>{he=w.dirname(ge(import.meta.url)),ve=w.resolve(he,"..","templates","starter")});import{visit as we}from"unist-util-visit";import{toString as xe}from"mdast-util-to-string";import be from"github-slugger";import{valueToEstree as Se}from"estree-util-value-to-estree";function E(){return e=>{let t=new be,o=[];we(e,"heading",s=>{let l=xe(s);o.push({depth:s.depth,id:t.slug(l),label:l})});let n={children:[]},r=[{node:n,depth:0}];for(let s of o){for(;r.length>1&&r[r.length-1].depth>=s.depth;)r.pop();let l={id:s.id,label:s.label},a=r[r.length-1].node;a.children||(a.children=[]),a.children.push(l),r.push({node:l,depth:s.depth})}let i=n.children;e.children.unshift({type:"mdxjsEsm",value:"",data:{estree:{type:"Program",sourceType:"module",body:[{type:"ExportNamedDeclaration",specifiers:[],declaration:{type:"VariableDeclaration",kind:"const",declarations:[{type:"VariableDeclarator",id:{type:"Identifier",name:"toc"},init:Se(i)}]}}]}}})}}var C=g(()=>{});import ke from"node:fs/promises";import I from"node:path";function N(e={},t){let o=e.colors?.primary??$e;return{name:e.name??t??"docs",colors:{primary:o,light:e.colors?.light??o,dark:e.colors?.dark??o},favicon:e.favicon??null,font:{family:e.font?.family??Pe},navigation:e.navigation??null}}async function z(e){let t=I.join(e,"velu.json"),o;try{o=await ke.readFile(t,"utf-8")}catch(r){throw r.code==="ENOENT"?new Error(`No velu.json found in ${e}. Run \`velu init <name>\` to scaffold one.`):r}let n;try{n=JSON.parse(o)}catch(r){throw new Error(`velu.json is not valid JSON: ${r.message}`)}return N(n,I.basename(e))}function V(e){let{colors:t,font:o}=e;return[":root:root {",` --accent-color: ${t.light};`,` --font-sans: "${o.family}", sans-serif;`,"}",':root:root[data-theme="dark"] {',` --accent-color: ${t.dark};`,"}"].join(`
|
|
4
|
+
`)}var $e,Pe,B=g(()=>{$e="#dc143c",Pe="Google Sans Flex"});import Ae from"github-slugger";import Te from"iso-639-1";function Ne(e){let t=String(e).toLowerCase();return Te.getNativeName(t)||e}function P(e){return new Ae().slug(String(e??""))}function Fe(e){let t=String(e).replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);return t[t.length-1]==="index"&&t.pop(),"/"+t.join("/")}function G(e){let t={kind:"root",children:b(e??{})};return D(t.children),t}function b(e){let t=Array.isArray(e.anchors)?e.anchors.map(Re):[];if(Array.isArray(e.products))return[...t,...e.products.map(Oe)];if(Array.isArray(e.versions))return[...t,...e.versions.map(Ue)];if(Array.isArray(e.languages))return[...t,...e.languages.map(je)];if(Array.isArray(e.tabs))return[...t,...e.tabs.map(Le)];let o=[...t];return Array.isArray(e.groups)&&o.push(...e.groups.map(M)),Array.isArray(e.pages)&&o.push(...e.pages.map(J)),o}function Oe(e){return{kind:"product",label:e.name??e.product,slug:P(e.product),icon:e.icon,color:e.color,default:!!e.default,children:b(e)}}function Ue(e){return{kind:"version",label:e.version,slug:P(e.version),default:!!e.default,children:b(e)}}function je(e){let t=String(e.language);return{kind:"language",code:t,label:Ne(t),slug:P(t),default:!!e.default,children:b(e)}}function Le(e){return{kind:"tab",label:e.tab,slug:P(e.tab),icon:e.icon,href:e.href,children:b(e)}}function Re(e){return{kind:"anchor",label:e.anchor,icon:e.icon,href:e.href,color:e.color}}function M(e){let t=(e.pages??[]).map(J);return e.root&&t.unshift({kind:"page",pagePath:e.root}),{kind:"group",label:e.group,icon:e.icon,expanded:!!e.expanded,root:e.root,children:t}}function J(e){return typeof e=="string"?{kind:"page",pagePath:e}:M(e)}function D(e){for(let t of Ee){let o=e.filter(n=>n.kind===t);o.length&&!o.some(n=>n.default)&&(o[0].default=!0)}for(let t of e)t.children&&D(t.children)}function q(e){let t=[],o=(n,r,i)=>{if(n.kind==="page"){t.push({pagePath:n.pagePath,url:_e(n.pagePath,r),ctx:{...r},ancestors:i,node:n});return}let s=r;n.kind==="product"?s={...r,product:n.default?null:n.slug}:n.kind==="version"?s={...r,version:n.default?null:n.slug}:n.kind==="language"&&(s={...r,language:n.default?null:n.slug});let l=n.kind==="root"?i:[...i,n];for(let a of n.children??[])o(a,s,l)};return o(e,{product:null,version:null,language:null},[]),t}function _e(e,t){let o=[t.product,t.version,t.language].filter(Boolean),n=Fe(e).split("/").filter(Boolean);return"/"+[...o,...n].join("/")}var Ee,F=g(()=>{Ee=["product","version","language"]});import Ce from"node:fs";import H from"node:path";function W(e,t){let o=[],n=[],r=new Set;for(let i of q(e)){if(r.has(i.url)){o.push(`duplicate URL "${i.url}" (page "${i.pagePath}") \u2014 keeping the first occurrence`);continue}r.add(i.url);let s=i.pagePath.replace(/^\/+|\/+$/g,""),l=H.join(t,...s.split("/"))+".mdx",a=Ce.existsSync(l);a||o.push(`page "${i.pagePath}" \u2192 ${H.relative(t,l)} not found`),n.push({pagePath:i.pagePath,url:i.url,fileAbsPath:l,exists:a})}return{pageEntries:n,warnings:o}}var Y=g(()=>{F()});import Ie from"node:fs";import K from"node:path";function Z(e){return{name:"velu-site",resolveId(t){return t===Q?X:null},load(t){return t!==X?null:ze(e)}}}function ze(e){let t={};try{t=JSON.parse(Ie.readFileSync(K.join(e,"velu.json"),"utf-8"))}catch{}let o=G(t.navigation??{}),{pageEntries:n,warnings:r}=W(o,e);for(let a of r)console.warn(`[velu] nav: ${a}`);let i=[],s=[],l=0;for(let a of n)if(a.exists){let A=JSON.stringify(a.fileAbsPath.split(K.sep).join("/")),p=`__p${l++}`;i.push(`import * as ${p} from ${A};`),s.push(` ${JSON.stringify(a.url)}: { Component: ${p}.default, frontmatter: ${p}.frontmatter, toc: ${p}.toc },`)}else s.push(` ${JSON.stringify(a.url)}: { missing: true },`);return["// AUTO-GENERATED by vite-plugin-velu-site. Do not edit.",i.join(`
|
|
5
|
+
`),"","export const pages = {",s.join(`
|
|
6
|
+
`),"};",`export const navigation = ${JSON.stringify(o)};`,""].join(`
|
|
7
|
+
`)}var Q,X,ee=g(()=>{F();Y();Q="virtual:velu-site",X="\0"+Q});var re={};j(re,{startDevServer:()=>at});import Ve from"node:fs/promises";import te from"node:fs";import c from"node:path";import{fileURLToPath as Be}from"node:url";import{createRequire as Ge}from"node:module";import Me from"chokidar";import Je from"express";import De from"@tailwindcss/vite";import qe from"@vitejs/plugin-react";import He from"@mdx-js/rollup";import We from"remark-frontmatter";import Ye from"remark-mdx-frontmatter";import Ke from"remark-gfm";import Xe from"rehype-slug";import{createServer as Qe,searchForWorkspaceRoot as Ze}from"vite";function rt(e){return`<link rel="stylesheet" href="${`https://fonts.googleapis.com/css2?family=${encodeURIComponent(e).replace(/%20/g,"+")}:wght@300;400;500;600;700&display=swap`}" />`}async function ne(e){try{return await z(e)}catch(t){return console.warn(`[velu] ${t.message} Using defaults.`),N({},c.basename(e))}}async function it(e,t){let o=await e.moduleGraph.getModuleByUrl(t,!0),n=new Map,r=new Set;async function i(s){if(!(!s||r.has(s.url))){if(r.add(s.url),s.id&&tt.test(s.id)){let l=s.url.includes("?")?"&":"?",a=await e.transformRequest(`${s.url}${l}direct`);a?.code&&n.set(s.url,a.code)}for(let l of s.importedModules)await i(l)}}return await i(o),[...n.values()].join(`
|
|
8
|
+
`)}async function at(e){console.log(`[velu-cli] dev server for: ${e}`);let t=await ne(e),o=e;try{o=te.realpathSync(e)}catch{}let r=Ge(import.meta.url).resolve("@mdx-js/react"),i=c.resolve(S,"runtime","velu-ui"),s=te.existsSync(i)?i:c.resolve(S,"..","velu-ui","src"),l=[{find:"@mdx-js/react",replacement:r},{find:/^velu-ui$/,replacement:c.join(s,"index.js")},{find:/^velu-ui\//,replacement:s+"/"}],a=Number(process.env.PORT)||5173,A=a+1,p=Je(),d=await Qe({root:S,appType:"custom",plugins:[Z(e),He({remarkPlugins:[We,Ye,Ke,E],rehypePlugins:[Xe],providerImportSource:"@mdx-js/react"}),qe(),De()],optimizeDeps:{esbuildOptions:{loader:{".mdx":"jsx"}}},resolve:{alias:l,dedupe:["react","react-dom","@mdx-js/react"]},server:{middlewareMode:!0,fs:{allow:[Ze(S),e,o]},hmr:{port:A}}});p.use(d.middlewares),p.get(oe,async(v,u,x)=>{if(!t.favicon)return u.status(404).end();let m=c.resolve(e,t.favicon.replace(/^\//,""));u.sendFile(m,f=>{f&&u.status(404).end()})}),p.use("*",async(v,u,x)=>{let m=v.originalUrl;try{let f=await Ve.readFile(st,"utf-8");f=await d.transformIndexHtml(m,f);let k="/src/runtime/server-entry.jsx",{render:$}=await d.ssrLoadModule(k),T=await $(m),U=await it(d,k),le=U?`<style data-velu-ssr>${U}</style>`:"",ce=`<style data-velu-config>
|
|
9
|
+
${V(t)}
|
|
10
|
+
</style>`,ue=ot.has(t.font.family)?"":rt(t.font.family),fe=t.favicon?`<link rel="icon" href="${oe}" />`:"",pe=[le,ce,ue,fe].filter(Boolean).join(`
|
|
11
|
+
`),de=f.replace("<!--app-title-->",nt(t.name)).replace("<!--app-head-->",pe).replace("<!--app-html-->",T);u.status(200).set({"Content-Type":"text/html"}).end(de)}catch(f){d.ssrFixStacktrace(f),x(f)}}),Me.watch(e,{ignoreInitial:!0,ignored:/(^|[\\/])(node_modules|\.git)([\\/]|$)/,awaitWriteFinish:{stabilityThreshold:100,pollInterval:20}}).on("all",async(v,u)=>{console.log(`[velu-cli] content ${v}: ${c.relative(e,u)}`);let x=c.basename(u)==="velu.json",m=u.endsWith(".mdx"),f=x||m&&(v==="add"||v==="unlink");if(x&&(t=await ne(e)),f)d.moduleGraph.invalidateAll();else if(m){let k=u.split(c.sep).join("/"),$=d.moduleGraph.getModulesByFile(k);if($)for(let T of $)d.moduleGraph.invalidateModule(T)}d.ws.send({type:"full-reload"})}),p.listen(a,()=>{console.log(`[velu-cli] listening on http://localhost:${a}`)})}var et,tt,ot,oe,nt,S,st,ie=g(()=>{C();B();ee();et=c.dirname(Be(import.meta.url)),tt=/\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?|$)/,ot=new Set(["Google Sans Flex","Google Sans Code","Outfit"]),oe="/@velu-favicon",nt=e=>String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");S=c.resolve(et,".."),st=c.resolve(S,"src","template.html")});import se from"node:path";import h from"node:process";var[,,ae,O]=h.argv;async function lt(){switch(ae){case"init":{O||(console.error("Usage: velu init <project-name>"),h.exit(1));let e=O,t=se.resolve(h.cwd(),e),{initProject:o}=await Promise.resolve().then(()=>(_(),R));await o(t,e);break}case"dev":{let e=se.resolve(h.cwd(),O??"."),{startDevServer:t}=await Promise.resolve().then(()=>(ie(),re));await t(e);break}default:console.log("Usage:"),console.log(" velu init <project-name> scaffold a new docs project"),console.log(" velu dev [dir] start the dev preview server"),h.exit(ae?1:0)}}lt().catch(e=>{console.error(h.env.VELU_DEBUG?e:`Error: ${e.message}`),h.exit(1)});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veluai/velu",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": "./dist/cli.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/**",
|
|
11
|
+
"src/runtime/**",
|
|
12
|
+
"src/navigation.js",
|
|
13
|
+
"src/template.html",
|
|
14
|
+
"runtime/velu-ui/**",
|
|
15
|
+
"schema/**",
|
|
16
|
+
"templates/**"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "node scripts/build.mjs",
|
|
20
|
+
"prepack": "node scripts/build.mjs"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@icons-pack/react-simple-icons": "^13.0.0",
|
|
24
|
+
"@mdx-js/react": "^3.1.1",
|
|
25
|
+
"@mdx-js/rollup": "^3.1.1",
|
|
26
|
+
"@tailwindcss/vite": "^4.1.0",
|
|
27
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
28
|
+
"chokidar": "^3.6.0",
|
|
29
|
+
"estree-util-value-to-estree": "^3.5.0",
|
|
30
|
+
"express": "^4.21.1",
|
|
31
|
+
"github-slugger": "^2.0.0",
|
|
32
|
+
"iso-639-1": "^3.1.5",
|
|
33
|
+
"lucide-react": "^0.460.0",
|
|
34
|
+
"mdast-util-to-string": "^4.0.0",
|
|
35
|
+
"prism-react-renderer": "^2.4.0",
|
|
36
|
+
"prismjs": "^1.29.0",
|
|
37
|
+
"react": "^18.3.1",
|
|
38
|
+
"react-dom": "^18.3.1",
|
|
39
|
+
"react-router-dom": "^6.28.0",
|
|
40
|
+
"rehype-slug": "^6.0.0",
|
|
41
|
+
"remark-frontmatter": "^5.0.0",
|
|
42
|
+
"remark-gfm": "^4.0.1",
|
|
43
|
+
"remark-mdx-frontmatter": "^5.2.0",
|
|
44
|
+
"tailwindcss": "^4.1.0",
|
|
45
|
+
"unist-util-visit": "^5.1.0",
|
|
46
|
+
"vite": "^5.4.11"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"esbuild": "^0.28.0",
|
|
50
|
+
"velu-ui": "0.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/* Fonts are loaded via <link rel="preconnect"/"stylesheet"> in template.html
|
|
2
|
+
(faster than an @import inside critical CSS — non-blocking, parallel). */
|
|
3
|
+
:root {
|
|
4
|
+
--ratio: 1.25;
|
|
5
|
+
--s-6: calc(var(--s-5) / var(--ratio));
|
|
6
|
+
--s-5: calc(var(--s-4) / var(--ratio));
|
|
7
|
+
--s-4: calc(var(--s-3) / var(--ratio));
|
|
8
|
+
--s-3: calc(var(--s-2) / var(--ratio));
|
|
9
|
+
--s-2: calc(var(--s-1) / var(--ratio));
|
|
10
|
+
--s-1: calc(var(--s0) / var(--ratio));
|
|
11
|
+
--s0: 1rem;
|
|
12
|
+
--s1: calc(var(--s0) * var(--ratio));
|
|
13
|
+
--s2: calc(var(--s1) * var(--ratio));
|
|
14
|
+
--s3: calc(var(--s2) * var(--ratio));
|
|
15
|
+
--s4: calc(var(--s3) * var(--ratio));
|
|
16
|
+
--s5: calc(var(--s4) * var(--ratio));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Semantic color tokens — verbatim from Figma (1:1 names, no translation).
|
|
20
|
+
Components reference ONLY these (never raw hex), so theme switching =
|
|
21
|
+
swapping this block (zero component changes).
|
|
22
|
+
--page-bg is PROVISIONAL: Figma has no explicit page-background token yet
|
|
23
|
+
(see note to Aravind) — replace once defined. */
|
|
24
|
+
:root {
|
|
25
|
+
--page-bg: #ffffff;
|
|
26
|
+
|
|
27
|
+
--border-color: #dcdcde;
|
|
28
|
+
--text-color: #000000;
|
|
29
|
+
--accent-color: #dc143c;
|
|
30
|
+
--muted-color: #7a7a7a;
|
|
31
|
+
--surface-color: #f2f3f4;
|
|
32
|
+
|
|
33
|
+
--note-bg-color: #f2f3f4;
|
|
34
|
+
--note-stroke-color: #6b7280;
|
|
35
|
+
--warning-bg-color: #fef3e2;
|
|
36
|
+
--warning-stroke-color: #d97706;
|
|
37
|
+
--info-bg-color: #e8f4fd;
|
|
38
|
+
--info-stroke-color: #3b82f6;
|
|
39
|
+
--tip-bg-color: #e8f5e9;
|
|
40
|
+
--tip-stroke-color: #16a34a;
|
|
41
|
+
--check-bg-color: #e6f9ed;
|
|
42
|
+
--check-stroke-color: #22c55e;
|
|
43
|
+
--danger-bg-color: #fde8e8;
|
|
44
|
+
--danger-stroke-color: #dc2626;
|
|
45
|
+
--callout-bg-color: #f3e8fd;
|
|
46
|
+
--callout-stroke-color: #8b5cf6;
|
|
47
|
+
|
|
48
|
+
--post-pill-color: #e8f4fd;
|
|
49
|
+
--post-pill-stroke-color: #91bbf9;
|
|
50
|
+
--post-cta-color: #3064e3;
|
|
51
|
+
--get-pill-color: #e6f9ed;
|
|
52
|
+
--get-pill-stroke-color: #22c55e;
|
|
53
|
+
--get-cta-color: #22c55e;
|
|
54
|
+
--put-pill-color: #fef3e2;
|
|
55
|
+
--put-pill-stroke-color: #d97706;
|
|
56
|
+
--put-cta-color: #d97706;
|
|
57
|
+
--delete-pill-color: #fde8e8;
|
|
58
|
+
--delete-pill-stroke-color: #dc2626;
|
|
59
|
+
--delete-cta-color: #dc2626;
|
|
60
|
+
--patch-pill-color: #f3e8fd;
|
|
61
|
+
--patch-pill-stroke-color: #8b5cf6;
|
|
62
|
+
--patch-cta-color: #8b5cf6;
|
|
63
|
+
}
|
|
64
|
+
:root[data-theme="dark"] {
|
|
65
|
+
--page-bg: #0D0E10;
|
|
66
|
+
|
|
67
|
+
--border-color: #262827;
|
|
68
|
+
--text-color: #eeeff1;
|
|
69
|
+
--accent-color: #dc143c;
|
|
70
|
+
--muted-color: #7a7a7a;
|
|
71
|
+
--surface-color: #323334;
|
|
72
|
+
|
|
73
|
+
--note-bg-color: #1a1a1c;
|
|
74
|
+
--note-stroke-color: #eeeff1;
|
|
75
|
+
--warning-bg-color: #2a1f0d;
|
|
76
|
+
--warning-stroke-color: #7a5a1f;
|
|
77
|
+
--info-bg-color: #0d1f2a;
|
|
78
|
+
--info-stroke-color: #1f5a7a;
|
|
79
|
+
--tip-bg-color: #0d2a1a;
|
|
80
|
+
--tip-stroke-color: #1f7a4a;
|
|
81
|
+
--check-bg-color: #0d2a1a;
|
|
82
|
+
--check-stroke-color: #1f7a4a;
|
|
83
|
+
--danger-bg-color: #2a0d0d;
|
|
84
|
+
--danger-stroke-color: #7a1f1f;
|
|
85
|
+
--callout-bg-color: #1f0d2a;
|
|
86
|
+
--callout-stroke-color: #5a1f7a;
|
|
87
|
+
|
|
88
|
+
--post-pill-color: #1e3a8a;
|
|
89
|
+
--post-pill-stroke-color: #3b82f6;
|
|
90
|
+
--post-cta-color: #2563eb;
|
|
91
|
+
--get-pill-color: #065f46;
|
|
92
|
+
--get-pill-stroke-color: #10b981;
|
|
93
|
+
--get-cta-color: #059669;
|
|
94
|
+
--put-pill-color: #78350f;
|
|
95
|
+
--put-pill-stroke-color: #f59e0b;
|
|
96
|
+
--put-cta-color: #d97706;
|
|
97
|
+
--delete-pill-color: #7f1d1d;
|
|
98
|
+
--delete-pill-stroke-color: #ef4444;
|
|
99
|
+
--delete-cta-color: #dc2626;
|
|
100
|
+
--patch-pill-color: #4c1d95;
|
|
101
|
+
--patch-pill-stroke-color: #8b5cf6;
|
|
102
|
+
--patch-cta-color: #7c3aed;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:root {
|
|
106
|
+
--measure: 66ch;
|
|
107
|
+
--font-sans: "Google Sans Flex", sans-serif;
|
|
108
|
+
--font-mono: "Google Sans Code", ui-monospace, SFMono-Regular, monospace;
|
|
109
|
+
--font-brand: "Outfit", var(--font-sans);
|
|
110
|
+
font-family: var(--font-sans);
|
|
111
|
+
|
|
112
|
+
/* Approx height of the docs PageHeader (used as the offset for sticky
|
|
113
|
+
sidebars / TOC so they stick BELOW the header, not behind it). */
|
|
114
|
+
--velu-header-height: 7rem;
|
|
115
|
+
|
|
116
|
+
/* Fluid font sizes — 375px → 1440px viewport */
|
|
117
|
+
--f-body: 1rem; /* 16 → 16 */
|
|
118
|
+
--f-h7: calc(var(--f-h6) / var(--ratio)); /* micro UI text — one modular step below h6 (chips, captions, kbd) */
|
|
119
|
+
--f-h6: clamp(0.75rem, 0.188vw + 0.68rem, 0.875rem); /* 12 → 14 (reversed: mobile 14 → desktop 12, see note) */
|
|
120
|
+
--f-h5: 1rem; /* 16 → 16 */
|
|
121
|
+
--f-h4: clamp(1.125rem, 0.235vw + 1.07rem, 1.375rem); /* 18 → 22 */
|
|
122
|
+
--f-h3: clamp(1.3125rem,0.423vw + 1.214rem, 1.75rem); /* 21 → 28 */
|
|
123
|
+
--f-h2: clamp(1.5rem, 0.704vw + 1.335rem, 2.25rem); /* 24 → 36 */
|
|
124
|
+
--f-h1: clamp(1.6875rem,1.972vw + 1.226rem, 3rem); /* 27 → 48 */
|
|
125
|
+
|
|
126
|
+
/* Line-height tokens — unitless (height ÷ font-size from Figma). The
|
|
127
|
+
single source of truth for leading; components reference these, never
|
|
128
|
+
a hardcoded ratio. */
|
|
129
|
+
--lh-body: 1.75;
|
|
130
|
+
--lh-h1: 1.75;
|
|
131
|
+
--lh-h2: 1.78;
|
|
132
|
+
--lh-h3: 1.71;
|
|
133
|
+
--lh-h4: 1.82;
|
|
134
|
+
--lh-h5: 1.75;
|
|
135
|
+
--lh-h6: 1.71;
|
|
136
|
+
--lh-h7: 1.71;
|
|
137
|
+
|
|
138
|
+
/* Hairline width — single source of truth for every box border, divider
|
|
139
|
+
and rail. (Sub-pixel: crisp on hi-dpi; some 1x displays round it.) */
|
|
140
|
+
--border-width: 0.5px;
|
|
141
|
+
|
|
142
|
+
/* Icon stroke weight — single source of truth for every lucide icon. */
|
|
143
|
+
--icon-stroke: 1.5;
|
|
144
|
+
|
|
145
|
+
/* Icon size scale — em-relative so an icon next to text inherits the
|
|
146
|
+
text size automatically. `--icon-size` matches surrounding text;
|
|
147
|
+
`--icon-size-lg` is one modular step larger (for nav lists / trees
|
|
148
|
+
where the icon should slightly outweigh the label). */
|
|
149
|
+
--icon-size: 1em;
|
|
150
|
+
--icon-size-lg: calc(1em * var(--ratio));
|
|
151
|
+
|
|
152
|
+
/* Corner-radius scale, derived from --s0 (16px) — no raw literals.
|
|
153
|
+
sm 8 / md 16 / lg 32. Components pick the right step. */
|
|
154
|
+
--radius-sm: calc(var(--s0) / 2);
|
|
155
|
+
--radius-md: var(--s0);
|
|
156
|
+
--radius-lg: calc(var(--s0) * 2);
|
|
157
|
+
|
|
158
|
+
/* Font-weight scale — single source of truth so components never
|
|
159
|
+
write raw 300/400/500/600/700. Matches Google Sans Flex's
|
|
160
|
+
variable-font axis stops. */
|
|
161
|
+
--weight-light: 300;
|
|
162
|
+
--weight-normal: 400;
|
|
163
|
+
--weight-medium: 500;
|
|
164
|
+
--weight-semibold: 600;
|
|
165
|
+
--weight-bold: 700;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* lucide-react adds class="lucide …" to every icon svg. This one rule
|
|
169
|
+
tokenizes stroke weight app-wide (CSS overrides the svg's stroke-width
|
|
170
|
+
attribute), so no component sets it per-icon. */
|
|
171
|
+
.lucide {
|
|
172
|
+
stroke-width: var(--icon-stroke);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Prose rhythm — wraps MDX content. MDX compiles to a flat list of
|
|
176
|
+
sibling block elements (h1, p, the velu-ui components, etc.).
|
|
177
|
+
Spacing is scaled by HEADING LEVEL so the document's structure is
|
|
178
|
+
readable at a glance:
|
|
179
|
+
- default sibling pair (paragraph rhythm): --s1
|
|
180
|
+
- before any heading (section break): scaled by level
|
|
181
|
+
- after a heading (heading → first child): one step under default
|
|
182
|
+
The rules use adjacent-sibling selectors so every same-level heading
|
|
183
|
+
gets the same lead-in regardless of what came before it (sibling
|
|
184
|
+
equality), and the larger before-heading gap reads as the section
|
|
185
|
+
break in every case. The owl rule sets the default; per-heading
|
|
186
|
+
rules below override it via source order (same specificity). */
|
|
187
|
+
|
|
188
|
+
/* Scroll-anchor offset for in-page links: when a TOC click lands on
|
|
189
|
+
a heading, the browser scrolls so the heading sits at the viewport
|
|
190
|
+
top — which would otherwise put it BEHIND the sticky chrome
|
|
191
|
+
(header + TocBar). `scroll-margin-block-start` tells the scroll
|
|
192
|
+
algorithm to leave that much space above the target.
|
|
193
|
+
|
|
194
|
+
--velu-scroll-offset is published by App.jsx as
|
|
195
|
+
`headerHeight + tocbarHeight + 1rem`, measured live via
|
|
196
|
+
ResizeObserver. The scroll-spy reads the SAME variable as its
|
|
197
|
+
trigger line, so scrollIntoView lands the heading exactly where
|
|
198
|
+
the spy promotes it — no magic numbers, no drift. */
|
|
199
|
+
.velu-hero h1,
|
|
200
|
+
.velu-prose :is(h1, h2, h3, h4, h5, h6) {
|
|
201
|
+
scroll-margin-block-start: var(--velu-scroll-offset, 5rem);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Default prose rhythm. */
|
|
205
|
+
.velu-prose > * + * {
|
|
206
|
+
margin-block-start: var(--s1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Before-heading: progressively larger gap above higher-level headings.
|
|
210
|
+
Same level → same gap → siblings read at equal intervals. */
|
|
211
|
+
.velu-prose > * + h2 {
|
|
212
|
+
margin-block-start: var(--s4);
|
|
213
|
+
}
|
|
214
|
+
.velu-prose > * + h3 {
|
|
215
|
+
margin-block-start: var(--s3);
|
|
216
|
+
}
|
|
217
|
+
.velu-prose > * + h4 {
|
|
218
|
+
margin-block-start: var(--s2);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* After-heading: tighter, so the heading reads as "introducing" what
|
|
222
|
+
follows rather than floating alone above a chasm. */
|
|
223
|
+
.velu-prose > h1 + *,
|
|
224
|
+
.velu-prose > h2 + *,
|
|
225
|
+
.velu-prose > h3 + *,
|
|
226
|
+
.velu-prose > h4 + *,
|
|
227
|
+
.velu-prose > h5 + *,
|
|
228
|
+
.velu-prose > h6 + * {
|
|
229
|
+
margin-block-start: var(--s0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Page hero — the frontmatter (title + description) is wrapped in
|
|
233
|
+
`.velu-hero` so the title and description sit tight as a single
|
|
234
|
+
unit, and a larger break separates the hero from the prose body. */
|
|
235
|
+
.velu-hero > h1 + p {
|
|
236
|
+
margin-block-start: var(--s-2);
|
|
237
|
+
}
|
|
238
|
+
/* hero → body is an h1 → h2 transition (level-down, not peer-to-peer),
|
|
239
|
+
so the break is one modular step LARGER than the regular section
|
|
240
|
+
break above an h2 (--s4). Keeps the scale strictly monotonic:
|
|
241
|
+
level-down break > peer break. */
|
|
242
|
+
.velu-hero + .velu-prose {
|
|
243
|
+
margin-block-start: var(--s5);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Sidebars / TOC scroll container: discreet thin scrollbar with a
|
|
247
|
+
tokenised colour in every state.
|
|
248
|
+
|
|
249
|
+
No transitions: Firefox doesn't honour CSS transitions on
|
|
250
|
+
`scrollbar-color` consistently, and the in-between interpolation +
|
|
251
|
+
Firefox's auto-derived hover/active colours can flash a yellow/amber
|
|
252
|
+
accent (it picks an OS / system accent when interpolating). Hard
|
|
253
|
+
switches avoid that. Same reason every WebKit thumb pseudo-state is
|
|
254
|
+
set explicitly — otherwise Chromium can fall back to the Windows 11
|
|
255
|
+
system accent on hover. */
|
|
256
|
+
.velu-hide-scrollbar {
|
|
257
|
+
scrollbar-width: thin;
|
|
258
|
+
/* Single colour both at rest and on hover. Firefox auto-darkens on
|
|
259
|
+
interaction, but starting from a defined light grey keeps the
|
|
260
|
+
derived shade in the same family (no yellow flash). */
|
|
261
|
+
scrollbar-color: var(--border-color) transparent;
|
|
262
|
+
}
|
|
263
|
+
.velu-hide-scrollbar::-webkit-scrollbar {
|
|
264
|
+
inline-size: 6px;
|
|
265
|
+
block-size: 6px;
|
|
266
|
+
}
|
|
267
|
+
.velu-hide-scrollbar::-webkit-scrollbar-track {
|
|
268
|
+
background: transparent;
|
|
269
|
+
}
|
|
270
|
+
.velu-hide-scrollbar::-webkit-scrollbar-thumb {
|
|
271
|
+
background: var(--border-color);
|
|
272
|
+
border-radius: 999px;
|
|
273
|
+
}
|
|
274
|
+
.velu-hide-scrollbar::-webkit-scrollbar-thumb:hover,
|
|
275
|
+
.velu-hide-scrollbar::-webkit-scrollbar-thumb:active {
|
|
276
|
+
background: var(--muted-color);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Unitless line-height ratios (height ÷ font-size from Figma) */
|
|
280
|
+
body {
|
|
281
|
+
font-size: var(--f-body);
|
|
282
|
+
line-height: var(--lh-body);
|
|
283
|
+
font-weight: var(--weight-normal);
|
|
284
|
+
background: var(--page-bg);
|
|
285
|
+
color: var(--text-color);
|
|
286
|
+
}
|
|
287
|
+
h1 { font-size: var(--f-h1); line-height: var(--lh-h1); font-weight: var(--weight-semibold); }
|
|
288
|
+
h2 { font-size: var(--f-h2); line-height: var(--lh-h2); font-weight: var(--weight-medium); }
|
|
289
|
+
h3 { font-size: var(--f-h3); line-height: var(--lh-h3); font-weight: var(--weight-medium); }
|
|
290
|
+
h4 { font-size: var(--f-h4); line-height: var(--lh-h4); font-weight: var(--weight-normal); }
|
|
291
|
+
h5 { font-size: var(--f-h5); line-height: var(--lh-h5); font-weight: var(--weight-normal); }
|
|
292
|
+
h6 { font-size: var(--f-h6); line-height: var(--lh-h6); font-weight: var(--weight-normal); }
|
|
293
|
+
|
|
294
|
+
* {
|
|
295
|
+
max-width: var(--measure);
|
|
296
|
+
box-sizing: border-box;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
html,
|
|
300
|
+
body,
|
|
301
|
+
div,
|
|
302
|
+
header,
|
|
303
|
+
nav,
|
|
304
|
+
main,
|
|
305
|
+
footer,
|
|
306
|
+
pre,
|
|
307
|
+
code,
|
|
308
|
+
span,
|
|
309
|
+
button {
|
|
310
|
+
max-width: none;
|
|
311
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Accordion + AccordionGroup — collapsible disclosure.
|
|
6
|
+
*
|
|
7
|
+
* Native <details>/<summary>: zero JS, SSR-safe, accessible, chevron rotates
|
|
8
|
+
* via CSS on [open] (same pattern as Sidebar). Fully tokenized — border /
|
|
9
|
+
* hover / expanded surface / radius / spacing / type all from base.css
|
|
10
|
+
* tokens, so light + dark theme automatically via [data-theme].
|
|
11
|
+
*
|
|
12
|
+
* - <Accordion title=… defaultOpen?>content</Accordion> — standalone bordered
|
|
13
|
+
* rounded box.
|
|
14
|
+
* - <AccordionGroup><Accordion/>…</AccordionGroup> — items joined into ONE
|
|
15
|
+
* bordered box with dividers between them (no per-item border/radius). A
|
|
16
|
+
* context tells nested Accordions they're grouped so they drop their own
|
|
17
|
+
* chrome; the group container owns the border + radius + dividers.
|
|
18
|
+
*
|
|
19
|
+
* @param {{ title: React.ReactNode, defaultOpen?: boolean,
|
|
20
|
+
* children: React.ReactNode, className?: string }} props
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const GroupCtx = React.createContext(false);
|
|
24
|
+
|
|
25
|
+
export function Accordion({
|
|
26
|
+
title,
|
|
27
|
+
defaultOpen = false,
|
|
28
|
+
children,
|
|
29
|
+
className = '',
|
|
30
|
+
...rest
|
|
31
|
+
}) {
|
|
32
|
+
const inGroup = React.useContext(GroupCtx);
|
|
33
|
+
const cls = [
|
|
34
|
+
'velu-accordion',
|
|
35
|
+
inGroup ? 'velu-accordion--in-group' : '',
|
|
36
|
+
className,
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(' ');
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<details className={cls} open={defaultOpen || undefined} {...rest}>
|
|
43
|
+
<summary className="velu-accordion__summary">
|
|
44
|
+
<span className="velu-accordion__chevron" aria-hidden="true">
|
|
45
|
+
{resolveIcon('chevron-right', { size: '1em' })}
|
|
46
|
+
</span>
|
|
47
|
+
<span className="velu-accordion__title">{title}</span>
|
|
48
|
+
</summary>
|
|
49
|
+
<div className="velu-accordion__content">{children}</div>
|
|
50
|
+
</details>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function AccordionGroup({ children, className = '', ...rest }) {
|
|
55
|
+
return (
|
|
56
|
+
<GroupCtx.Provider value={true}>
|
|
57
|
+
<div className={`velu-accordion-group ${className}`.trim()} {...rest}>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
</GroupCtx.Provider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default Accordion;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
+
import Cluster from '../primitives/Cluster.jsx';
|
|
4
|
+
import MethodBadge from './MethodBadge.jsx';
|
|
5
|
+
import TryItBar from './TryItBar.jsx';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ApiClient — the "try-it" playground layout shell.
|
|
9
|
+
*
|
|
10
|
+
* <ApiClient
|
|
11
|
+
* method="DELETE"
|
|
12
|
+
* label="Trigger Delete"
|
|
13
|
+
* path="/project/preview/{projectId}"
|
|
14
|
+
* description="Trigger a documentation deployment…"
|
|
15
|
+
* onClose={() => …}
|
|
16
|
+
* aside={
|
|
17
|
+
* <CodeGroup>…response / samples / examples…</CodeGroup>
|
|
18
|
+
* }
|
|
19
|
+
* >
|
|
20
|
+
* <AccordionGroup>
|
|
21
|
+
* <Accordion title="Authorization">
|
|
22
|
+
* <ApiField name="Authorization" type="string" required
|
|
23
|
+
* prefix="Bearer" />
|
|
24
|
+
* </Accordion>
|
|
25
|
+
* <Accordion title="Path">
|
|
26
|
+
* <ApiField name="projectId" type="string" required />
|
|
27
|
+
* </Accordion>
|
|
28
|
+
* </AccordionGroup>
|
|
29
|
+
* </ApiClient>
|
|
30
|
+
*
|
|
31
|
+
* ApiClient is a pure layout shell — it owns the frame, the close
|
|
32
|
+
* button, the operation/endpoint head row, and the two-column body.
|
|
33
|
+
* It does NOT re-implement collapsibles or code panels:
|
|
34
|
+
*
|
|
35
|
+
* - LEFT column = `children` — compose the request sections with the
|
|
36
|
+
* shared <Accordion> / <AccordionGroup> + <ApiField>.
|
|
37
|
+
* - RIGHT column = `aside` — compose the response / code-sample
|
|
38
|
+
* panels with the shared <CodeBlock> / <CodeGroup>.
|
|
39
|
+
*
|
|
40
|
+
* CTAs (Send, the operation dropdown) are presentational stubs — wire
|
|
41
|
+
* `onSend` / a real selector when the OpenAPI adapter lands.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export default function ApiClient({
|
|
45
|
+
method = 'GET',
|
|
46
|
+
label,
|
|
47
|
+
path = '',
|
|
48
|
+
description,
|
|
49
|
+
onSend,
|
|
50
|
+
onClose,
|
|
51
|
+
aside,
|
|
52
|
+
children,
|
|
53
|
+
className = '',
|
|
54
|
+
...rest
|
|
55
|
+
}) {
|
|
56
|
+
const key = String(method).toLowerCase();
|
|
57
|
+
const cls = `velu-api-client velu-api-client--${key} ${className}`.trim();
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={cls} {...rest}>
|
|
61
|
+
{/* Close button row — only when shown as a modal (onClose set). */}
|
|
62
|
+
{onClose && (
|
|
63
|
+
<div className="velu-api-client__close-row">
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
className="velu-api-client__close"
|
|
67
|
+
onClick={onClose}
|
|
68
|
+
aria-label="Close"
|
|
69
|
+
>
|
|
70
|
+
{resolveIcon('x', { size: '1em' })}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{/* Head — operation dropdown (dummy) + TryItBar relabeled "Send".
|
|
76
|
+
Cluster keeps each at its natural width: the dropdown hugs its
|
|
77
|
+
text (flex: none), the bar fills the rest (flex: 1). When the
|
|
78
|
+
bar can't keep its min width, Cluster wraps it below the
|
|
79
|
+
dropdown. */}
|
|
80
|
+
<Cluster
|
|
81
|
+
space="var(--s-1)"
|
|
82
|
+
align="flex-start"
|
|
83
|
+
className="velu-api-client__head"
|
|
84
|
+
>
|
|
85
|
+
<div className="velu-api-client__op">
|
|
86
|
+
<MethodBadge method={method} />
|
|
87
|
+
{label && (
|
|
88
|
+
<span className="velu-api-client__op-label">{label}</span>
|
|
89
|
+
)}
|
|
90
|
+
<span className="velu-api-client__op-chevron" aria-hidden="true">
|
|
91
|
+
{resolveIcon('chevron-down', { size: '1em' })}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
<TryItBar
|
|
95
|
+
method={method}
|
|
96
|
+
path={path}
|
|
97
|
+
cta="Send"
|
|
98
|
+
onTry={onSend}
|
|
99
|
+
className="velu-api-client__bar"
|
|
100
|
+
/>
|
|
101
|
+
</Cluster>
|
|
102
|
+
|
|
103
|
+
{/* Two-column body — left: request sections, right: response /
|
|
104
|
+
code panels. */}
|
|
105
|
+
<div className="velu-api-client__body">
|
|
106
|
+
<div className="velu-api-client__left">
|
|
107
|
+
{label && (
|
|
108
|
+
<h3 className="velu-api-client__title">{label}</h3>
|
|
109
|
+
)}
|
|
110
|
+
{description && (
|
|
111
|
+
<p className="velu-api-client__description">{description}</p>
|
|
112
|
+
)}
|
|
113
|
+
{children}
|
|
114
|
+
</div>
|
|
115
|
+
{aside != null && (
|
|
116
|
+
<div className="velu-api-client__right">{aside}</div>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { useId } from 'react';
|
|
2
|
+
import Cluster from '../primitives/Cluster.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ApiField — an input-bearing field for the ApiClient playground.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from <Field> (which is doc-only — it documents a parameter
|
|
8
|
+
* but has no control). ApiField renders a real <input> the user types
|
|
9
|
+
* a value into when trying an endpoint.
|
|
10
|
+
*
|
|
11
|
+
* <ApiSection title="Path">
|
|
12
|
+
* <ApiField name="projectId" type="string" required>
|
|
13
|
+
* Your project ID. Copy it from the API keys page.
|
|
14
|
+
* </ApiField>
|
|
15
|
+
* </ApiSection>
|
|
16
|
+
*
|
|
17
|
+
* Layout — a flex-wrap pair:
|
|
18
|
+
* [ name + type + required ] [ prefix? input ]
|
|
19
|
+
* [ description … ]
|
|
20
|
+
* The info column (label + description) and the control column wrap
|
|
21
|
+
* onto separate lines on narrow widths; no media queries.
|
|
22
|
+
*
|
|
23
|
+
* - `name` parameter identifier (mono, accent)
|
|
24
|
+
* - `type` schema type → grey chip ("string", "boolean", …)
|
|
25
|
+
* - `required` boolean → red "required" pill
|
|
26
|
+
* - `default` pre-fills the input's value
|
|
27
|
+
* - `prefix` static text shown inside the input before the field
|
|
28
|
+
* (e.g. "Bearer" for an Authorization header)
|
|
29
|
+
* - `placeholder` input placeholder (defaults to `enter <name>`)
|
|
30
|
+
* - `value`/`onChange` optional controlled-input wiring; uncontrolled
|
|
31
|
+
* otherwise
|
|
32
|
+
* - `children` description block
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export default function ApiField({
|
|
36
|
+
name,
|
|
37
|
+
type,
|
|
38
|
+
required = false,
|
|
39
|
+
default: defaultValue,
|
|
40
|
+
prefix,
|
|
41
|
+
placeholder,
|
|
42
|
+
value,
|
|
43
|
+
onChange,
|
|
44
|
+
children,
|
|
45
|
+
className = '',
|
|
46
|
+
...rest
|
|
47
|
+
}) {
|
|
48
|
+
const inputId = useId();
|
|
49
|
+
const controlled = value !== undefined;
|
|
50
|
+
return (
|
|
51
|
+
<div className={`velu-api-field ${className}`.trim()} {...rest}>
|
|
52
|
+
<div className="velu-api-field__info">
|
|
53
|
+
<Cluster space="var(--s-1)" align="baseline">
|
|
54
|
+
<label className="velu-api-field__name" htmlFor={inputId}>
|
|
55
|
+
{name}
|
|
56
|
+
</label>
|
|
57
|
+
{type != null && (
|
|
58
|
+
<span className="velu-api-field__chip">{type}</span>
|
|
59
|
+
)}
|
|
60
|
+
{required && (
|
|
61
|
+
<span className="velu-api-field__required">required</span>
|
|
62
|
+
)}
|
|
63
|
+
</Cluster>
|
|
64
|
+
{children != null && children !== false && (
|
|
65
|
+
<div className="velu-api-field__desc">{children}</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
<div className="velu-api-field__control">
|
|
69
|
+
{prefix != null && (
|
|
70
|
+
<span className="velu-api-field__prefix">{prefix}</span>
|
|
71
|
+
)}
|
|
72
|
+
<input
|
|
73
|
+
id={inputId}
|
|
74
|
+
className="velu-api-field__input"
|
|
75
|
+
type="text"
|
|
76
|
+
placeholder={placeholder ?? `enter ${name}`}
|
|
77
|
+
{...(controlled
|
|
78
|
+
? { value, onChange }
|
|
79
|
+
: {
|
|
80
|
+
defaultValue:
|
|
81
|
+
defaultValue != null ? String(defaultValue) : undefined,
|
|
82
|
+
})}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|