@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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Prism } from 'prism-react-renderer';
|
|
2
|
+
import { LANG_DEPS, LANG_ALIASES, PRISM_LOADERS } from './prism-langs.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lazy prism language loader — fetches grammars on demand based on the
|
|
6
|
+
* actual `language` props rendered, never bundles all 297 upfront.
|
|
7
|
+
*
|
|
8
|
+
* - `Prism` is exposed on globalThis so prismjs/components/*.js files
|
|
9
|
+
* (which look it up globally) find it when they register grammars.
|
|
10
|
+
* - `ensureLang(id)` walks the LANG_DEPS map to load every prerequisite
|
|
11
|
+
* in correct order before the target, then resolves once everything is
|
|
12
|
+
* registered. Subsequent calls for the same id share the cached promise.
|
|
13
|
+
* - Aliases (e.g. `rb` → `ruby`) are resolved up front via LANG_ALIASES.
|
|
14
|
+
* - `hasLang(id)` is a sync check used by render to gate highlighting:
|
|
15
|
+
* true once `Prism.languages[id]` exists OR the bundled fallback covers
|
|
16
|
+
* it. The CodeBlock kicks off ensureLang in an effect, then re-renders
|
|
17
|
+
* once `hasLang` flips true.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
if (typeof globalThis !== 'undefined' && !globalThis.Prism) {
|
|
21
|
+
globalThis.Prism = Prism;
|
|
22
|
+
} else if (typeof window !== 'undefined' && !window.Prism) {
|
|
23
|
+
window.Prism = Prism;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const loadPromises = new Map();
|
|
27
|
+
|
|
28
|
+
export function resolveLang(idOrAlias) {
|
|
29
|
+
if (!idOrAlias) return null;
|
|
30
|
+
if (LANG_ALIASES[idOrAlias]) return LANG_ALIASES[idOrAlias];
|
|
31
|
+
return idOrAlias;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function hasLang(id) {
|
|
35
|
+
if (!id) return true;
|
|
36
|
+
const canonical = resolveLang(id);
|
|
37
|
+
return Boolean(Prism.languages[canonical]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function loadOne(id) {
|
|
41
|
+
const loader = PRISM_LOADERS[id];
|
|
42
|
+
if (!loader) return;
|
|
43
|
+
try {
|
|
44
|
+
await loader();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.warn(`[velu-ui] failed to load prism lang: ${id}`, e?.message ?? e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function ensureLang(idOrAlias) {
|
|
51
|
+
const id = resolveLang(idOrAlias);
|
|
52
|
+
if (!id) return Promise.resolve();
|
|
53
|
+
if (Prism.languages[id]) return Promise.resolve();
|
|
54
|
+
let p = loadPromises.get(id);
|
|
55
|
+
if (p) return p;
|
|
56
|
+
|
|
57
|
+
p = (async () => {
|
|
58
|
+
// Load all dependencies first (recursive, sequential at each level).
|
|
59
|
+
const deps = LANG_DEPS[id] ?? [];
|
|
60
|
+
for (const dep of deps) {
|
|
61
|
+
const depId = resolveLang(dep);
|
|
62
|
+
if (depId && !Prism.languages[depId]) {
|
|
63
|
+
await ensureLang(depId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Load the target language itself.
|
|
67
|
+
if (!Prism.languages[id]) {
|
|
68
|
+
await loadOne(id);
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
loadPromises.set(id, p);
|
|
73
|
+
return p;
|
|
74
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { icons as lucideIcons } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve an icon prop to a renderable node — shared by Callout, Sidebar, etc.
|
|
6
|
+
*
|
|
7
|
+
* lucide convention: an icon's id is kebab-case; its component / `icons`-map
|
|
8
|
+
* key is the PascalCase form. So "triangle-alert" → "TriangleAlert" →
|
|
9
|
+
* lucideIcons.TriangleAlert.
|
|
10
|
+
*
|
|
11
|
+
* - string → resolved as a lucide id (content-driven; serializable)
|
|
12
|
+
* - node → returned as-is (power users / custom elements)
|
|
13
|
+
* - nullish or unknown id → null
|
|
14
|
+
*
|
|
15
|
+
* `iconProps` is spread onto the resolved lucide component (e.g. size,
|
|
16
|
+
* strokeWidth). Callers that style the icon via a CSS wrapper (Sidebar) can
|
|
17
|
+
* omit it; callers that size inline (Callout) pass it.
|
|
18
|
+
*/
|
|
19
|
+
export default function resolveIcon(icon, iconProps = {}) {
|
|
20
|
+
if (icon == null) return null;
|
|
21
|
+
if (typeof icon !== 'string') return icon;
|
|
22
|
+
const key = icon
|
|
23
|
+
.split(/[-_\s]+/)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
26
|
+
.join('');
|
|
27
|
+
const Cmp = lucideIcons[key];
|
|
28
|
+
return Cmp ? <Cmp {...iconProps} /> : null;
|
|
29
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scrollIntoNearestView — programmatic "scroll so this element is visible"
|
|
3
|
+
* that reliably honours `scroll-padding-top` / `scroll-padding-bottom` on
|
|
4
|
+
* the nearest scrollable ancestor.
|
|
5
|
+
*
|
|
6
|
+
* Why not `el.scrollIntoView({ block: 'nearest' })`?
|
|
7
|
+
* Both Chrome and Firefox implement that opt sporadically with respect
|
|
8
|
+
* to scroll-padding-* — sometimes treating the full scrollport as the
|
|
9
|
+
* target instead of the scroll-padded inset. That matters here because
|
|
10
|
+
* docs sidebars + TOC live behind a footer that visually eclipses their
|
|
11
|
+
* bottoms; we use `scroll-padding-bottom` to mark that band as off-limits.
|
|
12
|
+
* Computing the delta manually guarantees we always pull the element
|
|
13
|
+
* above the footer's eclipsed band, on every engine.
|
|
14
|
+
*
|
|
15
|
+
* The optional `padBottom` (and `padTop`) override the values read from
|
|
16
|
+
* the parent's computed style. This is needed when the visible region's
|
|
17
|
+
* bottom is driven by a *live* DOM measurement (e.g. a fixed/sticky
|
|
18
|
+
* footer's getBoundingClientRect) — that value tracks the user's actual
|
|
19
|
+
* scroll position synchronously, whereas CSS scroll-padding-* driven by
|
|
20
|
+
* React state lags behind committed renders and can leave the active
|
|
21
|
+
* item flush against the footer on fast scrolls.
|
|
22
|
+
*
|
|
23
|
+
* Behaviour:
|
|
24
|
+
* - finds the nearest ancestor with `overflow-y: auto | scroll`
|
|
25
|
+
* - does nothing if `el` is already inside that ancestor's
|
|
26
|
+
* (effective) scroll-padded visible region — no jitter when the
|
|
27
|
+
* active item is already on-screen
|
|
28
|
+
* - otherwise scrolls the ancestor by the minimum delta (matching the
|
|
29
|
+
* `block: 'nearest'` contract)
|
|
30
|
+
*
|
|
31
|
+
* @param {HTMLElement} el target element
|
|
32
|
+
* @param {{ padTop?: number, padBottom?: number }} [opts]
|
|
33
|
+
*/
|
|
34
|
+
export default function scrollIntoNearestView(el, opts = {}) {
|
|
35
|
+
if (!el) return;
|
|
36
|
+
let parent = el.parentElement;
|
|
37
|
+
while (parent && parent !== document.body) {
|
|
38
|
+
const overflowY = getComputedStyle(parent).overflowY;
|
|
39
|
+
if (overflowY === 'auto' || overflowY === 'scroll') break;
|
|
40
|
+
parent = parent.parentElement;
|
|
41
|
+
}
|
|
42
|
+
if (!parent || parent === document.body) return;
|
|
43
|
+
|
|
44
|
+
const elRect = el.getBoundingClientRect();
|
|
45
|
+
const parentRect = parent.getBoundingClientRect();
|
|
46
|
+
const cs = getComputedStyle(parent);
|
|
47
|
+
const padTop = opts.padTop ?? (parseFloat(cs.scrollPaddingTop) || 0);
|
|
48
|
+
const padBottom =
|
|
49
|
+
opts.padBottom ?? (parseFloat(cs.scrollPaddingBottom) || 0);
|
|
50
|
+
|
|
51
|
+
const visibleTop = parentRect.top + padTop;
|
|
52
|
+
const visibleBottom = parentRect.bottom - padBottom;
|
|
53
|
+
|
|
54
|
+
let delta = 0;
|
|
55
|
+
if (elRect.top < visibleTop) delta = elRect.top - visibleTop;
|
|
56
|
+
else if (elRect.bottom > visibleBottom) delta = elRect.bottom - visibleBottom;
|
|
57
|
+
if (delta === 0) return;
|
|
58
|
+
|
|
59
|
+
// Direct `scrollTop` assignment (not scrollBy/scrollTo with options):
|
|
60
|
+
// - universally supported (no behavior:'instant' compat concerns),
|
|
61
|
+
// - synchronous,
|
|
62
|
+
// - can't be cancelled by a subsequent scroll event,
|
|
63
|
+
// which matters because we're often called many times in quick
|
|
64
|
+
// succession from scroll-spy updates while the user is mid-scroll.
|
|
65
|
+
parent.scrollTop = parent.scrollTop + delta;
|
|
66
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Callout from './components/Callout.jsx';
|
|
3
|
+
import Card, { CardGroup } from './components/Card.jsx';
|
|
4
|
+
import Accordion, { AccordionGroup } from './components/Accordion.jsx';
|
|
5
|
+
import Columns from './components/Columns.jsx';
|
|
6
|
+
import Field from './components/Field.jsx';
|
|
7
|
+
import Prompt from './components/Prompt.jsx';
|
|
8
|
+
import Steps, { Step } from './components/Steps.jsx';
|
|
9
|
+
import Tree, { Folder, File } from './components/Tree.jsx';
|
|
10
|
+
import Image from './components/Image.jsx';
|
|
11
|
+
import CodeBlock, { CodeGroup } from './components/CodeBlock.jsx';
|
|
12
|
+
import MethodBadge from './components/MethodBadge.jsx';
|
|
13
|
+
import ApiPath from './components/ApiPath.jsx';
|
|
14
|
+
import TryItBar from './components/TryItBar.jsx';
|
|
15
|
+
import ApiField from './components/ApiField.jsx';
|
|
16
|
+
import ApiClient from './components/ApiClient.jsx';
|
|
17
|
+
import ApiSidebar from './components/ApiSidebar.jsx';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* defaultMdxComponents — the registry the MDXProvider consumes.
|
|
21
|
+
*
|
|
22
|
+
* Two kinds of entries:
|
|
23
|
+
*
|
|
24
|
+
* 1. Lowercase keys ('pre', 'h1', …) override the HTML tags that
|
|
25
|
+
* Markdown produces. We use these to upgrade plain ``` fenced
|
|
26
|
+
* code blocks into our CodeBlock component, etc.
|
|
27
|
+
*
|
|
28
|
+
* 2. Capitalised keys ('Callout', 'Card', …) match the exact tag
|
|
29
|
+
* names authors write in their .mdx, e.g. `<Callout type="warn">`.
|
|
30
|
+
* MDX looks up the name in this map and renders our component
|
|
31
|
+
* instead of treating it as an unknown HTML element.
|
|
32
|
+
*
|
|
33
|
+
* Tag-name → component is the only contract here. SSR-safe by
|
|
34
|
+
* construction: identical map on server + client → identical render.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/* `pre > code` is how Markdown renders ``` blocks. We pull the
|
|
38
|
+
language out of the code-element's className ("language-js" → "js")
|
|
39
|
+
and forward children verbatim to CodeBlock. */
|
|
40
|
+
function PreOverride({ children }) {
|
|
41
|
+
const child = React.Children.only(children);
|
|
42
|
+
const className = child?.props?.className || '';
|
|
43
|
+
const m = /language-(\S+)/.exec(className);
|
|
44
|
+
const language = m ? m[1] : undefined;
|
|
45
|
+
const code =
|
|
46
|
+
typeof child?.props?.children === 'string'
|
|
47
|
+
? child.props.children.replace(/\n$/, '')
|
|
48
|
+
: child?.props?.children;
|
|
49
|
+
return (
|
|
50
|
+
<CodeBlock language={language}>
|
|
51
|
+
{code}
|
|
52
|
+
</CodeBlock>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const defaultMdxComponents = {
|
|
57
|
+
/* HTML-tag overrides (lowercase keys). */
|
|
58
|
+
pre: PreOverride,
|
|
59
|
+
|
|
60
|
+
/* Authored component tags (capitalised keys). */
|
|
61
|
+
Callout,
|
|
62
|
+
Card,
|
|
63
|
+
CardGroup,
|
|
64
|
+
Accordion,
|
|
65
|
+
AccordionGroup,
|
|
66
|
+
Columns,
|
|
67
|
+
Field,
|
|
68
|
+
Prompt,
|
|
69
|
+
Steps,
|
|
70
|
+
Step,
|
|
71
|
+
Tree,
|
|
72
|
+
Folder,
|
|
73
|
+
File,
|
|
74
|
+
Image,
|
|
75
|
+
CodeBlock,
|
|
76
|
+
CodeGroup,
|
|
77
|
+
MethodBadge,
|
|
78
|
+
ApiPath,
|
|
79
|
+
TryItBar,
|
|
80
|
+
ApiField,
|
|
81
|
+
ApiClient,
|
|
82
|
+
ApiSidebar,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default defaultMdxComponents;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cluster — Every Layout primitive. Inline group that wraps onto
|
|
5
|
+
* multiple lines when it runs out of horizontal room. Use for chips,
|
|
6
|
+
* tags, action button rows, nav links, badge groups — any horizontal
|
|
7
|
+
* row of inline-ish items that should break gracefully.
|
|
8
|
+
*
|
|
9
|
+
* Like Stack, this is pure inline styles (no CSS file needed since
|
|
10
|
+
* there's no `> *` selector) — React-native, SSR-safe, tokenized.
|
|
11
|
+
* Mirrors the Every Layout custom element but as React with the same
|
|
12
|
+
* three knobs.
|
|
13
|
+
*
|
|
14
|
+
* - `space` gap between items (default --s1)
|
|
15
|
+
* - `justify` justify-content (default flex-start)
|
|
16
|
+
* - `align` align-items (default flex-start)
|
|
17
|
+
* - `as` element tag (default 'div')
|
|
18
|
+
*
|
|
19
|
+
* @param {object} props
|
|
20
|
+
* @param {React.ElementType} [props.as='div']
|
|
21
|
+
* @param {string} [props.space='var(--s1)']
|
|
22
|
+
* @param {string} [props.justify='flex-start']
|
|
23
|
+
* @param {string} [props.align='flex-start']
|
|
24
|
+
*/
|
|
25
|
+
export default function Cluster({
|
|
26
|
+
as: Tag = 'div',
|
|
27
|
+
space = 'var(--s1)',
|
|
28
|
+
justify = 'flex-start',
|
|
29
|
+
align = 'flex-start',
|
|
30
|
+
style,
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}) {
|
|
34
|
+
return (
|
|
35
|
+
<Tag
|
|
36
|
+
style={{
|
|
37
|
+
display: 'flex',
|
|
38
|
+
flexWrap: 'wrap',
|
|
39
|
+
justifyContent: justify,
|
|
40
|
+
alignItems: align,
|
|
41
|
+
gap: space,
|
|
42
|
+
...style,
|
|
43
|
+
}}
|
|
44
|
+
{...rest}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</Tag>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stack — vertical rhythm primitive (Every Layout), React-native.
|
|
5
|
+
*
|
|
6
|
+
* Spacing is delegated to flex `gap`, set inline per instance. This is
|
|
7
|
+
* leak-free and nesting-independent BY CONSTRUCTION: `gap` is resolved on the
|
|
8
|
+
* container element itself, never inherited by or computed on children — so a
|
|
9
|
+
* nested Stack's spacing cannot bleed into the gap its parent puts around it.
|
|
10
|
+
*
|
|
11
|
+
* `space` is any CSS length (defaulting to the --s1 modular-scale token).
|
|
12
|
+
*
|
|
13
|
+
* NOTE: `recursive` (rhythm at *every* nesting depth) is intentionally not
|
|
14
|
+
* supported here — `gap` only spaces direct children. That job belongs to a
|
|
15
|
+
* future Prose/Flow primitive (Markdown iteration), not to Stack.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} props
|
|
18
|
+
* @param {React.ElementType} [props.as='div'] element/component to render as
|
|
19
|
+
* @param {string} [props.space='var(--s1)'] gap between items (CSS length/token)
|
|
20
|
+
* @param {number|null} [props.splitAfter=null] 1-based child index; everything
|
|
21
|
+
* after it is pushed to the bottom (margin-block-end:auto on that child)
|
|
22
|
+
*/
|
|
23
|
+
const Stack = React.forwardRef(function Stack(
|
|
24
|
+
{
|
|
25
|
+
as: Tag = 'div',
|
|
26
|
+
space = 'var(--s1)',
|
|
27
|
+
splitAfter = null,
|
|
28
|
+
style,
|
|
29
|
+
children,
|
|
30
|
+
...rest
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) {
|
|
34
|
+
let kids = children;
|
|
35
|
+
if (splitAfter) {
|
|
36
|
+
kids = React.Children.map(children, (child, i) =>
|
|
37
|
+
i === splitAfter - 1 && React.isValidElement(child)
|
|
38
|
+
? React.cloneElement(child, {
|
|
39
|
+
style: { ...child.props.style, marginBlockEnd: 'auto' },
|
|
40
|
+
})
|
|
41
|
+
: child
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Tag
|
|
47
|
+
ref={ref}
|
|
48
|
+
style={{
|
|
49
|
+
display: 'flex',
|
|
50
|
+
flexDirection: 'column',
|
|
51
|
+
justifyContent: 'flex-start',
|
|
52
|
+
gap: space,
|
|
53
|
+
...(splitAfter ? { blockSize: '100%' } : null),
|
|
54
|
+
...style,
|
|
55
|
+
}}
|
|
56
|
+
{...rest}
|
|
57
|
+
>
|
|
58
|
+
{kids}
|
|
59
|
+
</Tag>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default Stack;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Switcher — Every Layout primitive. Row ↔ column at a container-width point,
|
|
5
|
+
* with NO media queries: flex-wrap + flex-basis: calc((threshold - 100%)*999).
|
|
6
|
+
* All-or-nothing (never multi-row wrap).
|
|
7
|
+
*
|
|
8
|
+
* Two ways to set the flip point:
|
|
9
|
+
* - `threshold`: the container width at which it flips — the raw Every
|
|
10
|
+
* Layout knob (default --measure token).
|
|
11
|
+
* - `min`: a per-ITEM minimum width (more intuitive). The Switcher knows its
|
|
12
|
+
* child count, so it derives the container threshold as
|
|
13
|
+
* N * min + (N - 1) * space
|
|
14
|
+
* → the row collapses to a full vertical stack the instant any one item
|
|
15
|
+
* would fall below `min`. `min` takes precedence over `threshold`.
|
|
16
|
+
*
|
|
17
|
+
* Other props:
|
|
18
|
+
* - `space` gap between items (default --s1 token)
|
|
19
|
+
* - `limit` hard cap of items in a row; beyond it ALL items stack
|
|
20
|
+
* - `as` element to render (semantics; default 'div')
|
|
21
|
+
*
|
|
22
|
+
* React-native + SSR-safe + tokenized (vs. the browser-only web component
|
|
23
|
+
* which injects a runtime <style>). Needs its own CSS (styles `> *`).
|
|
24
|
+
*/
|
|
25
|
+
export default function Switcher({
|
|
26
|
+
as: Tag = 'div',
|
|
27
|
+
space = 'var(--s1)',
|
|
28
|
+
threshold = 'var(--measure)',
|
|
29
|
+
min,
|
|
30
|
+
limit,
|
|
31
|
+
className = '',
|
|
32
|
+
style,
|
|
33
|
+
children,
|
|
34
|
+
...rest
|
|
35
|
+
}) {
|
|
36
|
+
const count = React.Children.count(children);
|
|
37
|
+
const flip =
|
|
38
|
+
min != null
|
|
39
|
+
? `calc((${min} * ${count}) + (${space} * ${Math.max(0, count - 1)}))`
|
|
40
|
+
: threshold;
|
|
41
|
+
const exceeded = typeof limit === 'number' && count > limit;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Tag
|
|
45
|
+
className={`velu-switcher ${className}`.trim()}
|
|
46
|
+
data-limit-exceeded={exceeded ? '' : undefined}
|
|
47
|
+
style={{
|
|
48
|
+
'--switcher-space': space,
|
|
49
|
+
'--switcher-threshold': flip,
|
|
50
|
+
...style,
|
|
51
|
+
}}
|
|
52
|
+
{...rest}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</Tag>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Switcher — Every Layout. All values are tokens (passed via the
|
|
2
|
+
--switcher-* custom properties set inline by Switcher.jsx). Needs the
|
|
3
|
+
`> *` selector, so it has CSS unlike Stack. */
|
|
4
|
+
|
|
5
|
+
.velu-switcher {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-wrap: wrap;
|
|
8
|
+
gap: var(--switcher-space, var(--s1));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* The trick: when the row's width drops below `threshold`, the negative
|
|
12
|
+
product flips flex-basis huge→huge-negative, forcing items onto their
|
|
13
|
+
own lines (vertical). No media query. */
|
|
14
|
+
.velu-switcher > * {
|
|
15
|
+
flex-grow: 1;
|
|
16
|
+
flex-basis: calc(
|
|
17
|
+
(var(--switcher-threshold, var(--measure)) - 100%) * 999
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* `limit` exceeded: force every item full-width (always vertical). Set as a
|
|
22
|
+
static attribute from React's child count — no runtime :nth-last-child. */
|
|
23
|
+
.velu-switcher[data-limit-exceeded] > * {
|
|
24
|
+
flex-basis: 100%;
|
|
25
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Aggregate stylesheet for velu-ui. Consumers import this once.
|
|
2
|
+
|
|
3
|
+
Tailwind v4 first (theme/preflight/utilities — all in @layer).
|
|
4
|
+
base.css is intentionally UNLAYERED, so it wins over Tailwind's
|
|
5
|
+
preflight `base` layer — our fluid type/measure beat Tailwind's reset
|
|
6
|
+
by cascade-layer precedence (unlayered > layered). */
|
|
7
|
+
@import 'tailwindcss';
|
|
8
|
+
|
|
9
|
+
/* Map Tailwind's `dark:` variant onto our [data-theme="dark"] attribute
|
|
10
|
+
(instead of Tailwind's default prefers-color-scheme). */
|
|
11
|
+
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
|
12
|
+
|
|
13
|
+
/* Tailwind's auto source-detection is rooted at the Vite root (velu-cli)
|
|
14
|
+
and ignores node_modules — velu-ui is reached via a workspace symlink
|
|
15
|
+
there, so its component classes would NOT be scanned. This @source is
|
|
16
|
+
relative to THIS file (packages/velu-ui/src/styles.css); "." == the
|
|
17
|
+
velu-ui src tree, so Tailwind also scans velu-ui components. */
|
|
18
|
+
@source ".";
|
|
19
|
+
|
|
20
|
+
@import './base.css';
|
|
21
|
+
@import './primitives/switcher.css';
|
|
22
|
+
@import './components/sidebar.css';
|
|
23
|
+
@import './components/nav-select.css';
|
|
24
|
+
@import './components/accordion.css';
|
|
25
|
+
@import './components/card.css';
|
|
26
|
+
@import './components/image.css';
|
|
27
|
+
@import './components/code-block.css';
|
|
28
|
+
@import './components/field.css';
|
|
29
|
+
@import './components/prompt.css';
|
|
30
|
+
@import './components/steps.css';
|
|
31
|
+
@import './components/tree.css';
|
|
32
|
+
@import './components/api.css';
|
|
33
|
+
@import './components/ask-bar.css';
|
|
34
|
+
@import './components/chatbot.css';
|
|
35
|
+
@import './components/page-feedback.css';
|
|
36
|
+
@import './components/page-nav.css';
|
|
37
|
+
@import './components/page-footer.css';
|
|
38
|
+
@import './components/powered-by.css';
|
|
39
|
+
@import './components/page-header.css';
|
|
40
|
+
@import './components/theme-toggle.css';
|
|
41
|
+
@import './components/docs-layout.css';
|
|
42
|
+
@import './components/toc-bar.css';
|
|
43
|
+
@import './components/search.css';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/* DEPRECATED — superseded by base.css (modular scale moved there, ratio
|
|
2
|
+
changed 1.5 → 1.25, --s-6 added, plus fonts/type/measure). Not imported
|
|
3
|
+
anywhere. Safe to delete; kept only because shell deletes are disabled
|
|
4
|
+
in this session. Do NOT re-import — two scale definitions = not DRY. */
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://veludocs.com/velu.schema.json",
|
|
4
|
+
"title": "Velu project configuration",
|
|
5
|
+
"description": "Configuration for a Velu documentation project (velu.json). This schema is defined internally during iteration; it will be hosted at the $id URL so editors can validate + autocomplete velu.json files.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "URL of this JSON Schema, enabling editor validation + autocomplete."
|
|
13
|
+
},
|
|
14
|
+
"name": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Project name; usually the folder name it's contained in.",
|
|
17
|
+
"minLength": 1
|
|
18
|
+
},
|
|
19
|
+
"colors": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "Accent colors. Maps onto the --accent-color token in base.css.",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": ["primary"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"primary": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "The accent color used across the UI (--accent-color)."
|
|
28
|
+
},
|
|
29
|
+
"light": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Light-mode accent override. Defaults to `primary`."
|
|
32
|
+
},
|
|
33
|
+
"dark": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Dark-mode accent override. Defaults to `primary`."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"favicon": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Path to the favicon, relative to the project root."
|
|
42
|
+
},
|
|
43
|
+
"font": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Typography. Maps onto the --font-sans token in base.css.",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"required": ["family"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"family": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Google Font family to use (--font-sans)."
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"navigation": {
|
|
56
|
+
"$ref": "#/$defs/navContainer",
|
|
57
|
+
"description": "Site navigation. Mintlify-compatible: products > versions > languages > tabs > groups > pages. Switchable axes (product/version/language) become URL path prefixes; the default value of each is unprefixed. Dropdowns are intentionally unsupported."
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"$defs": {
|
|
61
|
+
"navContainer": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"description": "A navigation container. Higher-precedence keys nest the lower ones (products contain versions contain languages contain tabs contain groups/pages).",
|
|
65
|
+
"properties": {
|
|
66
|
+
"products": { "type": "array", "items": { "$ref": "#/$defs/product" } },
|
|
67
|
+
"versions": { "type": "array", "items": { "$ref": "#/$defs/version" } },
|
|
68
|
+
"languages": { "type": "array", "items": { "$ref": "#/$defs/language" } },
|
|
69
|
+
"tabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } },
|
|
70
|
+
"anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
|
|
71
|
+
"groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
|
|
72
|
+
"pages": { "$ref": "#/$defs/pages" }
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"pages": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"description": "Ordered list of page paths (no extension, relative to the project root) and/or nested groups.",
|
|
78
|
+
"items": {
|
|
79
|
+
"oneOf": [
|
|
80
|
+
{ "type": "string" },
|
|
81
|
+
{ "$ref": "#/$defs/group" }
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"group": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"required": ["group"],
|
|
89
|
+
"properties": {
|
|
90
|
+
"group": { "type": "string", "description": "Sidebar section heading." },
|
|
91
|
+
"icon": { "type": "string", "description": "lucide icon id." },
|
|
92
|
+
"expanded": { "type": "boolean", "description": "Start expanded." },
|
|
93
|
+
"root": { "type": "string", "description": "Landing page path for the group." },
|
|
94
|
+
"pages": { "$ref": "#/$defs/pages" }
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"tab": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"additionalProperties": false,
|
|
100
|
+
"required": ["tab"],
|
|
101
|
+
"properties": {
|
|
102
|
+
"tab": { "type": "string", "description": "Tab label (top nav)." },
|
|
103
|
+
"icon": { "type": "string" },
|
|
104
|
+
"href": { "type": "string", "description": "External/override link instead of in-site pages." },
|
|
105
|
+
"anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
|
|
106
|
+
"groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
|
|
107
|
+
"pages": { "$ref": "#/$defs/pages" }
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"anchor": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"required": ["anchor", "href"],
|
|
114
|
+
"properties": {
|
|
115
|
+
"anchor": { "type": "string", "description": "Anchor label (pinned sidebar link)." },
|
|
116
|
+
"icon": { "type": "string" },
|
|
117
|
+
"href": { "type": "string" },
|
|
118
|
+
"color": { "type": "string" }
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"product": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"additionalProperties": false,
|
|
124
|
+
"required": ["product"],
|
|
125
|
+
"properties": {
|
|
126
|
+
"product": { "type": "string", "description": "Product id (used for the URL slug)." },
|
|
127
|
+
"name": { "type": "string", "description": "Display name; defaults to `product`." },
|
|
128
|
+
"icon": { "type": "string" },
|
|
129
|
+
"color": { "type": "string" },
|
|
130
|
+
"default": { "type": "boolean", "description": "The default product is unprefixed in URLs." },
|
|
131
|
+
"versions": { "type": "array", "items": { "$ref": "#/$defs/version" } },
|
|
132
|
+
"languages": { "type": "array", "items": { "$ref": "#/$defs/language" } },
|
|
133
|
+
"tabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } },
|
|
134
|
+
"anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
|
|
135
|
+
"groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
|
|
136
|
+
"pages": { "$ref": "#/$defs/pages" }
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"version": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"additionalProperties": false,
|
|
142
|
+
"required": ["version"],
|
|
143
|
+
"properties": {
|
|
144
|
+
"version": { "type": "string", "description": "Version label (used for the URL slug)." },
|
|
145
|
+
"default": { "type": "boolean", "description": "The default version is unprefixed in URLs." },
|
|
146
|
+
"languages": { "type": "array", "items": { "$ref": "#/$defs/language" } },
|
|
147
|
+
"tabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } },
|
|
148
|
+
"anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
|
|
149
|
+
"groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
|
|
150
|
+
"pages": { "$ref": "#/$defs/pages" }
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"language": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"additionalProperties": false,
|
|
156
|
+
"required": ["language"],
|
|
157
|
+
"properties": {
|
|
158
|
+
"language": { "type": "string", "description": "Language code (used for the URL slug)." },
|
|
159
|
+
"default": { "type": "boolean", "description": "The default language is unprefixed in URLs." },
|
|
160
|
+
"tabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } },
|
|
161
|
+
"anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
|
|
162
|
+
"groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
|
|
163
|
+
"pages": { "$ref": "#/$defs/pages" }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|