@veluai/velu 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +18 -18
  3. package/package.json +3 -2
  4. package/runtime/velu-ui/components/Accordion.jsx +64 -64
  5. package/runtime/velu-ui/components/ApiClient.jsx +121 -121
  6. package/runtime/velu-ui/components/ApiField.jsx +87 -87
  7. package/runtime/velu-ui/components/ApiPath.jsx +63 -63
  8. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -122
  9. package/runtime/velu-ui/components/AskBar.jsx +71 -71
  10. package/runtime/velu-ui/components/Callout.jsx +114 -114
  11. package/runtime/velu-ui/components/Card.jsx +131 -131
  12. package/runtime/velu-ui/components/Chatbot.jsx +596 -596
  13. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  14. package/runtime/velu-ui/components/Columns.jsx +56 -56
  15. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  16. package/runtime/velu-ui/components/Field.jsx +81 -81
  17. package/runtime/velu-ui/components/Image.jsx +163 -163
  18. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  19. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  20. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  21. package/runtime/velu-ui/components/PageFooter.jsx +213 -213
  22. package/runtime/velu-ui/components/PageHeader.jsx +414 -414
  23. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  24. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  25. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  26. package/runtime/velu-ui/components/Search.jsx +411 -366
  27. package/runtime/velu-ui/components/Sidebar.jsx +191 -191
  28. package/runtime/velu-ui/components/Steps.jsx +65 -65
  29. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  30. package/runtime/velu-ui/components/Toc.jsx +537 -537
  31. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  32. package/runtime/velu-ui/components/Tree.jsx +87 -87
  33. package/runtime/velu-ui/components/TryItBar.jsx +90 -90
  34. package/runtime/velu-ui/components/accordion.css +92 -92
  35. package/runtime/velu-ui/components/api.css +479 -479
  36. package/runtime/velu-ui/components/ask-bar.css +94 -94
  37. package/runtime/velu-ui/components/card.css +105 -105
  38. package/runtime/velu-ui/components/chatbot.css +617 -617
  39. package/runtime/velu-ui/components/code-block.css +263 -263
  40. package/runtime/velu-ui/components/docs-layout.css +784 -775
  41. package/runtime/velu-ui/components/field.css +82 -82
  42. package/runtime/velu-ui/components/image.css +237 -237
  43. package/runtime/velu-ui/components/nav-select.css +157 -157
  44. package/runtime/velu-ui/components/page-feedback.css +241 -241
  45. package/runtime/velu-ui/components/page-footer.css +130 -130
  46. package/runtime/velu-ui/components/page-header.css +520 -520
  47. package/runtime/velu-ui/components/page-nav.css +50 -50
  48. package/runtime/velu-ui/components/powered-by.css +66 -66
  49. package/runtime/velu-ui/components/prompt.css +99 -99
  50. package/runtime/velu-ui/components/search.css +307 -307
  51. package/runtime/velu-ui/components/sidebar.css +144 -144
  52. package/runtime/velu-ui/components/steps.css +77 -77
  53. package/runtime/velu-ui/components/theme-toggle.css +101 -101
  54. package/runtime/velu-ui/components/toc-bar.css +234 -234
  55. package/runtime/velu-ui/components/tree.css +49 -49
  56. package/runtime/velu-ui/index.js +46 -46
  57. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  58. package/runtime/velu-ui/lib/copyText.js +64 -64
  59. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  60. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  61. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  62. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  63. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  64. package/runtime/velu-ui/mdx-components.jsx +105 -105
  65. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  66. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  67. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  68. package/runtime/velu-ui/primitives/stack.css +3 -3
  69. package/runtime/velu-ui/primitives/switcher.css +25 -25
  70. package/runtime/velu-ui/styles.css +43 -43
  71. package/runtime/velu-ui/tokens.css +4 -4
  72. package/schema/velu.schema.json +167 -167
  73. package/src/lib/extract-mdx-error.js +170 -170
  74. package/src/lib/issues.js +159 -159
  75. package/src/lib/known-components.js +34 -34
  76. package/src/navigation.js +434 -434
  77. package/src/runtime/App.jsx +1490 -1476
  78. package/src/runtime/ErrorBoundary.jsx +54 -54
  79. package/src/runtime/client-entry.jsx +22 -22
  80. package/src/runtime/server-entry.jsx +16 -16
  81. package/src/template.html +48 -48
  82. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  83. package/templates/starter/ai-tools/cursor.mdx +17 -17
  84. package/templates/starter/api-reference/endpoint/create.mdx +24 -24
  85. package/templates/starter/api-reference/endpoint/get.mdx +27 -27
  86. package/templates/starter/api-reference/introduction.mdx +28 -28
  87. package/templates/starter/development.mdx +19 -19
  88. package/templates/starter/essentials/code.mdx +29 -29
  89. package/templates/starter/essentials/images.mdx +29 -29
  90. package/templates/starter/essentials/markdown.mdx +25 -25
  91. package/templates/starter/essentials/navigation.mdx +39 -39
  92. package/templates/starter/essentials/settings.mdx +30 -30
  93. package/templates/starter/favicon.svg +6 -6
  94. package/templates/starter/index.mdx +31 -31
  95. package/templates/starter/quickstart.mdx +31 -31
  96. package/templates/starter/velu.json +33 -33
@@ -1,100 +1,100 @@
1
- // Per-component option schemas — the props whose wrong value would otherwise
2
- // render incorrectly *silently*. Deliberately small and high-signal; extend a
3
- // component's entry here when it gains a new constrained option.
4
- //
5
- // Pure data + a pure validator (no React, no other imports) so BOTH sides can
6
- // use it: the live MDX registry (velu-ui/mdx-components.jsx) validates at
7
- // render time, and `velu validate` (velu-cli) imports this same module to
8
- // statically check literal attributes — one source of truth, no drift.
9
-
10
- // Canonical accepted values (must match the components):
11
- // Callout — VARIANTS keys; the component is CASE-SENSITIVE (VARIANTS[type]).
12
- // MethodBadge — METHODS; the component lower-cases the input first.
13
- export const CALLOUT_TYPES = ['note', 'warning', 'info', 'tip', 'check', 'danger', 'callout'];
14
- export const METHOD_NAMES = ['get', 'post', 'put', 'patch', 'delete'];
15
-
16
- // schema: { enums: { prop: { values, caseSensitive } }, required: [prop, …] }
17
- const SCHEMAS = {
18
- Callout: { enums: { type: { values: CALLOUT_TYPES, caseSensitive: true } } },
19
- MethodBadge: { enums: { method: { values: METHOD_NAMES, caseSensitive: false } } },
20
- ApiPath: { enums: { method: { values: METHOD_NAMES, caseSensitive: false } } },
21
- Image: { required: ['src'] },
22
- };
23
-
24
- // Smallest edit distance match within a set (for "did you mean"). Tiny inputs.
25
- function closest(value, values) {
26
- const v = String(value).toLowerCase();
27
- let best = null;
28
- let bestD = Infinity;
29
- for (const cand of values) {
30
- const a = v;
31
- const b = cand.toLowerCase();
32
- // inline Levenshtein
33
- const m = a.length;
34
- const n = b.length;
35
- const d = Array.from({ length: n + 1 }, (_, i) => i);
36
- for (let i = 1; i <= m; i++) {
37
- let prev = d[0];
38
- d[0] = i;
39
- for (let j = 1; j <= n; j++) {
40
- const tmp = d[j];
41
- d[j] = Math.min(d[j] + 1, d[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
42
- prev = tmp;
43
- }
44
- }
45
- if (d[n] < bestD) {
46
- bestD = d[n];
47
- best = cand;
48
- }
49
- }
50
- return bestD <= 3 ? best : null;
51
- }
52
-
53
- /**
54
- * Validate a component's props against its schema.
55
- * @param {string} name component tag name
56
- * @param {object} props props passed to it
57
- * @param {{ checkRequired?: boolean }} [opts] static callers pass
58
- * checkRequired:false because a prop supplied via a {expression}
59
- * isn't a literal they can see, so "missing required" would be a
60
- * false positive.
61
- * @returns an issue-shaped object (category 'invalid-props') or null if valid.
62
- * { category, title, detail, hint, suggestion }
63
- */
64
- export function validateProps(name, props = {}, opts = {}) {
65
- const { checkRequired = true } = opts;
66
- const schema = SCHEMAS[name];
67
- if (!schema) return null;
68
-
69
- for (const req of checkRequired ? schema.required || [] : []) {
70
- const v = props[req];
71
- if (v == null || v === '') {
72
- return {
73
- category: 'invalid-props',
74
- title: `<${name}> is missing the required "${req}" prop`,
75
- hint: `Add ${req}="…" to the component.`,
76
- };
77
- }
78
- }
79
-
80
- for (const [prop, rule] of Object.entries(schema.enums || {})) {
81
- if (!(prop in props) || props[prop] == null) continue;
82
- const raw = props[prop];
83
- const ok = rule.caseSensitive
84
- ? rule.values.includes(raw)
85
- : rule.values.map((s) => s.toLowerCase()).includes(String(raw).toLowerCase());
86
- if (!ok) {
87
- const guess = closest(raw, rule.values);
88
- return {
89
- category: 'invalid-props',
90
- title: `Invalid "${prop}" on <${name}>: "${raw}"`,
91
- detail: `Expected one of: ${rule.values.join(', ')}.`,
92
- hint: guess ? `Did you mean "${guess}"?` : '',
93
- };
94
- }
95
- }
96
-
97
- return null;
98
- }
99
-
100
- export default validateProps;
1
+ // Per-component option schemas — the props whose wrong value would otherwise
2
+ // render incorrectly *silently*. Deliberately small and high-signal; extend a
3
+ // component's entry here when it gains a new constrained option.
4
+ //
5
+ // Pure data + a pure validator (no React, no other imports) so BOTH sides can
6
+ // use it: the live MDX registry (velu-ui/mdx-components.jsx) validates at
7
+ // render time, and `velu validate` (velu-cli) imports this same module to
8
+ // statically check literal attributes — one source of truth, no drift.
9
+
10
+ // Canonical accepted values (must match the components):
11
+ // Callout — VARIANTS keys; the component is CASE-SENSITIVE (VARIANTS[type]).
12
+ // MethodBadge — METHODS; the component lower-cases the input first.
13
+ export const CALLOUT_TYPES = ['note', 'warning', 'info', 'tip', 'check', 'danger', 'callout'];
14
+ export const METHOD_NAMES = ['get', 'post', 'put', 'patch', 'delete'];
15
+
16
+ // schema: { enums: { prop: { values, caseSensitive } }, required: [prop, …] }
17
+ const SCHEMAS = {
18
+ Callout: { enums: { type: { values: CALLOUT_TYPES, caseSensitive: true } } },
19
+ MethodBadge: { enums: { method: { values: METHOD_NAMES, caseSensitive: false } } },
20
+ ApiPath: { enums: { method: { values: METHOD_NAMES, caseSensitive: false } } },
21
+ Image: { required: ['src'] },
22
+ };
23
+
24
+ // Smallest edit distance match within a set (for "did you mean"). Tiny inputs.
25
+ function closest(value, values) {
26
+ const v = String(value).toLowerCase();
27
+ let best = null;
28
+ let bestD = Infinity;
29
+ for (const cand of values) {
30
+ const a = v;
31
+ const b = cand.toLowerCase();
32
+ // inline Levenshtein
33
+ const m = a.length;
34
+ const n = b.length;
35
+ const d = Array.from({ length: n + 1 }, (_, i) => i);
36
+ for (let i = 1; i <= m; i++) {
37
+ let prev = d[0];
38
+ d[0] = i;
39
+ for (let j = 1; j <= n; j++) {
40
+ const tmp = d[j];
41
+ d[j] = Math.min(d[j] + 1, d[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
42
+ prev = tmp;
43
+ }
44
+ }
45
+ if (d[n] < bestD) {
46
+ bestD = d[n];
47
+ best = cand;
48
+ }
49
+ }
50
+ return bestD <= 3 ? best : null;
51
+ }
52
+
53
+ /**
54
+ * Validate a component's props against its schema.
55
+ * @param {string} name component tag name
56
+ * @param {object} props props passed to it
57
+ * @param {{ checkRequired?: boolean }} [opts] static callers pass
58
+ * checkRequired:false because a prop supplied via a {expression}
59
+ * isn't a literal they can see, so "missing required" would be a
60
+ * false positive.
61
+ * @returns an issue-shaped object (category 'invalid-props') or null if valid.
62
+ * { category, title, detail, hint, suggestion }
63
+ */
64
+ export function validateProps(name, props = {}, opts = {}) {
65
+ const { checkRequired = true } = opts;
66
+ const schema = SCHEMAS[name];
67
+ if (!schema) return null;
68
+
69
+ for (const req of checkRequired ? schema.required || [] : []) {
70
+ const v = props[req];
71
+ if (v == null || v === '') {
72
+ return {
73
+ category: 'invalid-props',
74
+ title: `<${name}> is missing the required "${req}" prop`,
75
+ hint: `Add ${req}="…" to the component.`,
76
+ };
77
+ }
78
+ }
79
+
80
+ for (const [prop, rule] of Object.entries(schema.enums || {})) {
81
+ if (!(prop in props) || props[prop] == null) continue;
82
+ const raw = props[prop];
83
+ const ok = rule.caseSensitive
84
+ ? rule.values.includes(raw)
85
+ : rule.values.map((s) => s.toLowerCase()).includes(String(raw).toLowerCase());
86
+ if (!ok) {
87
+ const guess = closest(raw, rule.values);
88
+ return {
89
+ category: 'invalid-props',
90
+ title: `Invalid "${prop}" on <${name}>: "${raw}"`,
91
+ detail: `Expected one of: ${rule.values.join(', ')}.`,
92
+ hint: guess ? `Did you mean "${guess}"?` : '',
93
+ };
94
+ }
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ export default validateProps;
@@ -1,64 +1,64 @@
1
- /**
2
- * copyText — copy a string to the clipboard, working in BOTH secure and
3
- * insecure contexts.
4
- *
5
- * `navigator.clipboard` only exists in a secure context (HTTPS or
6
- * localhost). On a plain-HTTP origin — e.g. previewing the dev server
7
- * over the LAN at http://<ip>:8358 on a phone — it's `undefined`, so a
8
- * `if (!navigator.clipboard) return` guard makes copy buttons silently
9
- * no-op. This util falls back to the legacy `execCommand('copy')` via a
10
- * hidden textarea so copy still works on those origins.
11
- *
12
- * Must be called from a user gesture (click) — both paths require it.
13
- * Returns a Promise: resolves on success, rejects on failure.
14
- */
15
- export default function copyText(text) {
16
- const str = String(text ?? '');
17
-
18
- // Preferred path — async Clipboard API (secure contexts only).
19
- if (
20
- typeof navigator !== 'undefined' &&
21
- navigator.clipboard &&
22
- typeof window !== 'undefined' &&
23
- window.isSecureContext
24
- ) {
25
- return navigator.clipboard.writeText(str);
26
- }
27
-
28
- // Fallback — hidden <textarea> + execCommand, for insecure origins.
29
- return new Promise((resolve, reject) => {
30
- if (typeof document === 'undefined') {
31
- reject(new Error('copyText: no document'));
32
- return;
33
- }
34
- const ta = document.createElement('textarea');
35
- ta.value = str;
36
- ta.setAttribute('readonly', '');
37
- // Keep it out of view and inert, but still selectable.
38
- ta.style.position = 'fixed';
39
- ta.style.top = '0';
40
- ta.style.left = '0';
41
- ta.style.inlineSize = '1px';
42
- ta.style.blockSize = '1px';
43
- ta.style.padding = '0';
44
- ta.style.border = 'none';
45
- ta.style.opacity = '0';
46
- document.body.appendChild(ta);
47
- ta.focus();
48
- ta.select();
49
- try {
50
- ta.setSelectionRange(0, str.length);
51
- } catch {
52
- /* some browsers reject setSelectionRange on a readonly field */
53
- }
54
- let ok = false;
55
- try {
56
- ok = document.execCommand('copy');
57
- } catch {
58
- ok = false;
59
- }
60
- document.body.removeChild(ta);
61
- if (ok) resolve();
62
- else reject(new Error('copyText: execCommand copy failed'));
63
- });
64
- }
1
+ /**
2
+ * copyText — copy a string to the clipboard, working in BOTH secure and
3
+ * insecure contexts.
4
+ *
5
+ * `navigator.clipboard` only exists in a secure context (HTTPS or
6
+ * localhost). On a plain-HTTP origin — e.g. previewing the dev server
7
+ * over the LAN at http://<ip>:8358 on a phone — it's `undefined`, so a
8
+ * `if (!navigator.clipboard) return` guard makes copy buttons silently
9
+ * no-op. This util falls back to the legacy `execCommand('copy')` via a
10
+ * hidden textarea so copy still works on those origins.
11
+ *
12
+ * Must be called from a user gesture (click) — both paths require it.
13
+ * Returns a Promise: resolves on success, rejects on failure.
14
+ */
15
+ export default function copyText(text) {
16
+ const str = String(text ?? '');
17
+
18
+ // Preferred path — async Clipboard API (secure contexts only).
19
+ if (
20
+ typeof navigator !== 'undefined' &&
21
+ navigator.clipboard &&
22
+ typeof window !== 'undefined' &&
23
+ window.isSecureContext
24
+ ) {
25
+ return navigator.clipboard.writeText(str);
26
+ }
27
+
28
+ // Fallback — hidden <textarea> + execCommand, for insecure origins.
29
+ return new Promise((resolve, reject) => {
30
+ if (typeof document === 'undefined') {
31
+ reject(new Error('copyText: no document'));
32
+ return;
33
+ }
34
+ const ta = document.createElement('textarea');
35
+ ta.value = str;
36
+ ta.setAttribute('readonly', '');
37
+ // Keep it out of view and inert, but still selectable.
38
+ ta.style.position = 'fixed';
39
+ ta.style.top = '0';
40
+ ta.style.left = '0';
41
+ ta.style.inlineSize = '1px';
42
+ ta.style.blockSize = '1px';
43
+ ta.style.padding = '0';
44
+ ta.style.border = 'none';
45
+ ta.style.opacity = '0';
46
+ document.body.appendChild(ta);
47
+ ta.focus();
48
+ ta.select();
49
+ try {
50
+ ta.setSelectionRange(0, str.length);
51
+ } catch {
52
+ /* some browsers reject setSelectionRange on a readonly field */
53
+ }
54
+ let ok = false;
55
+ try {
56
+ ok = document.execCommand('copy');
57
+ } catch {
58
+ ok = false;
59
+ }
60
+ document.body.removeChild(ta);
61
+ if (ok) resolve();
62
+ else reject(new Error('copyText: execCommand copy failed'));
63
+ });
64
+ }
@@ -1,147 +1,147 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { resolveLang } from './prism-loader.js';
3
-
4
- /**
5
- * Language → icon mapping.
6
- *
7
- * Brand icons (JS / Python / Ruby / etc.) come from
8
- * @icons-pack/react-simple-icons. We load each one with an explicit
9
- * dynamic `import()` (static specifier, so Vite code-splits one chunk per
10
- * icon) — the browser then fetches ONLY the languages a page actually
11
- * renders, never the full set and never the package's ~3000-icon barrel
12
- * (importing that barrel makes Vite transform all 3000 modules on the
13
- * first dev load, which dominated cold start).
14
- *
15
- * Because the import is dynamic, a brand icon isn't available during SSR;
16
- * `BrandIcon` renders a sized placeholder until the module resolves on the
17
- * client. The placeholder is identical on the server and the first client
18
- * render, so hydration never mismatches. Langs without a brand icon fall
19
- * back to a semantic lucide id (string) handled by `resolveIcon`.
20
- */
21
-
22
- // canonical lang → loader (() => import of that icon's module).
23
- const BRAND_LOADERS = {
24
- javascript: () => import('@icons-pack/react-simple-icons/icons/SiJavascript.mjs'),
25
- typescript: () => import('@icons-pack/react-simple-icons/icons/SiTypescript.mjs'),
26
- jsx: () => import('@icons-pack/react-simple-icons/icons/SiReact.mjs'),
27
- tsx: () => import('@icons-pack/react-simple-icons/icons/SiReact.mjs'),
28
- python: () => import('@icons-pack/react-simple-icons/icons/SiPython.mjs'),
29
- ruby: () => import('@icons-pack/react-simple-icons/icons/SiRuby.mjs'),
30
- go: () => import('@icons-pack/react-simple-icons/icons/SiGo.mjs'),
31
- rust: () => import('@icons-pack/react-simple-icons/icons/SiRust.mjs'),
32
- java: () => import('@icons-pack/react-simple-icons/icons/SiOpenjdk.mjs'),
33
- csharp: () => import('@icons-pack/react-simple-icons/icons/SiSharp.mjs'),
34
- c: () => import('@icons-pack/react-simple-icons/icons/SiC.mjs'),
35
- cpp: () => import('@icons-pack/react-simple-icons/icons/SiCplusplus.mjs'),
36
- php: () => import('@icons-pack/react-simple-icons/icons/SiPhp.mjs'),
37
- swift: () => import('@icons-pack/react-simple-icons/icons/SiSwift.mjs'),
38
- kotlin: () => import('@icons-pack/react-simple-icons/icons/SiKotlin.mjs'),
39
- scala: () => import('@icons-pack/react-simple-icons/icons/SiScala.mjs'),
40
- dart: () => import('@icons-pack/react-simple-icons/icons/SiDart.mjs'),
41
- r: () => import('@icons-pack/react-simple-icons/icons/SiR.mjs'),
42
- haskell: () => import('@icons-pack/react-simple-icons/icons/SiHaskell.mjs'),
43
- elixir: () => import('@icons-pack/react-simple-icons/icons/SiElixir.mjs'),
44
- erlang: () => import('@icons-pack/react-simple-icons/icons/SiErlang.mjs'),
45
- lua: () => import('@icons-pack/react-simple-icons/icons/SiLua.mjs'),
46
- perl: () => import('@icons-pack/react-simple-icons/icons/SiPerl.mjs'),
47
- julia: () => import('@icons-pack/react-simple-icons/icons/SiJulia.mjs'),
48
- ocaml: () => import('@icons-pack/react-simple-icons/icons/SiOcaml.mjs'),
49
- fsharp: () => import('@icons-pack/react-simple-icons/icons/SiFsharp.mjs'),
50
- nim: () => import('@icons-pack/react-simple-icons/icons/SiNim.mjs'),
51
- zig: () => import('@icons-pack/react-simple-icons/icons/SiZig.mjs'),
52
- clojure: () => import('@icons-pack/react-simple-icons/icons/SiClojure.mjs'),
53
- coffeescript: () => import('@icons-pack/react-simple-icons/icons/SiCoffeescript.mjs'),
54
- html: () => import('@icons-pack/react-simple-icons/icons/SiHtml5.mjs'),
55
- css: () => import('@icons-pack/react-simple-icons/icons/SiCss.mjs'),
56
- vue: () => import('@icons-pack/react-simple-icons/icons/SiVuedotjs.mjs'),
57
- svelte: () => import('@icons-pack/react-simple-icons/icons/SiSvelte.mjs'),
58
- solid: () => import('@icons-pack/react-simple-icons/icons/SiSolid.mjs'),
59
- json: () => import('@icons-pack/react-simple-icons/icons/SiJson.mjs'),
60
- yaml: () => import('@icons-pack/react-simple-icons/icons/SiYaml.mjs'),
61
- toml: () => import('@icons-pack/react-simple-icons/icons/SiToml.mjs'),
62
- graphql: () => import('@icons-pack/react-simple-icons/icons/SiGraphql.mjs'),
63
- markdown: () => import('@icons-pack/react-simple-icons/icons/SiMarkdown.mjs'),
64
- mdx: () => import('@icons-pack/react-simple-icons/icons/SiMarkdown.mjs'),
65
- latex: () => import('@icons-pack/react-simple-icons/icons/SiLatex.mjs'),
66
- asciidoc: () => import('@icons-pack/react-simple-icons/icons/SiAsciidoctor.mjs'),
67
- bash: () => import('@icons-pack/react-simple-icons/icons/SiGnubash.mjs'),
68
- shell: () => import('@icons-pack/react-simple-icons/icons/SiGnubash.mjs'),
69
- sh: () => import('@icons-pack/react-simple-icons/icons/SiGnubash.mjs'),
70
- fish: () => import('@icons-pack/react-simple-icons/icons/SiFishshell.mjs'),
71
- nodejs: () => import('@icons-pack/react-simple-icons/icons/SiNodedotjs.mjs'),
72
- mysql: () => import('@icons-pack/react-simple-icons/icons/SiMysql.mjs'),
73
- postgresql: () => import('@icons-pack/react-simple-icons/icons/SiPostgresql.mjs'),
74
- plsql: () => import('@icons-pack/react-simple-icons/icons/SiPostgresql.mjs'),
75
- sqlite: () => import('@icons-pack/react-simple-icons/icons/SiSqlite.mjs'),
76
- mongodb: () => import('@icons-pack/react-simple-icons/icons/SiMongodb.mjs'),
77
- redis: () => import('@icons-pack/react-simple-icons/icons/SiRedis.mjs'),
78
- docker: () => import('@icons-pack/react-simple-icons/icons/SiDocker.mjs'),
79
- dockerfile: () => import('@icons-pack/react-simple-icons/icons/SiDocker.mjs'),
80
- kubernetes: () => import('@icons-pack/react-simple-icons/icons/SiKubernetes.mjs'),
81
- nginx: () => import('@icons-pack/react-simple-icons/icons/SiNginx.mjs'),
82
- apacheconf: () => import('@icons-pack/react-simple-icons/icons/SiApache.mjs'),
83
- };
84
-
85
- // Generic lucide fallback for langs without a brand icon. Returns a
86
- // lucide id string — `resolveIcon` turns it into the icon component.
87
- const LUCIDE = {
88
- sql: 'database',
89
- xml: 'code-xml',
90
- diff: 'git-compare-arrows',
91
- ini: 'braces',
92
- properties: 'braces',
93
- powershell: 'terminal',
94
- batch: 'terminal',
95
- zsh: 'terminal',
96
- text: 'file-code',
97
- plaintext: 'file-code',
98
- };
99
-
100
- // Module-level cache so an icon that's been loaded once renders
101
- // synchronously everywhere after (no repeat import, no flash).
102
- const iconCache = new Map();
103
-
104
- /**
105
- * Renders a brand icon loaded lazily by `key`. SSR + the first client
106
- * render emit a sized placeholder (identical → hydration-safe); after
107
- * mount the icon's module is imported and swapped in.
108
- */
109
- function BrandIcon({ iconKey, ...props }) {
110
- const [Icon, setIcon] = useState(() => iconCache.get(iconKey) || null);
111
-
112
- useEffect(() => {
113
- if (iconCache.has(iconKey)) {
114
- setIcon(() => iconCache.get(iconKey));
115
- return;
116
- }
117
- const load = BRAND_LOADERS[iconKey];
118
- if (!load) return;
119
- let alive = true;
120
- load().then((mod) => {
121
- iconCache.set(iconKey, mod.default);
122
- if (alive) setIcon(() => mod.default);
123
- });
124
- return () => {
125
- alive = false;
126
- };
127
- }, [iconKey]);
128
-
129
- if (!Icon) {
130
- // Reserve the icon's box so swapping it in causes no layout shift.
131
- return (
132
- <span
133
- aria-hidden="true"
134
- style={{ display: 'inline-block', width: '1em', height: '1em' }}
135
- />
136
- );
137
- }
138
- return <Icon {...props} />;
139
- }
140
-
141
- export function iconForLang(lang) {
142
- if (!lang) return 'file-code';
143
- const id = resolveLang(lang) || lang;
144
- const key = BRAND_LOADERS[id] ? id : BRAND_LOADERS[lang] ? lang : null;
145
- if (key) return <BrandIcon iconKey={key} size="1em" />;
146
- return LUCIDE[id] || LUCIDE[lang] || 'file-code';
147
- }
1
+ import React, { useState, useEffect } from 'react';
2
+ import { resolveLang } from './prism-loader.js';
3
+
4
+ /**
5
+ * Language → icon mapping.
6
+ *
7
+ * Brand icons (JS / Python / Ruby / etc.) come from
8
+ * @icons-pack/react-simple-icons. We load each one with an explicit
9
+ * dynamic `import()` (static specifier, so Vite code-splits one chunk per
10
+ * icon) — the browser then fetches ONLY the languages a page actually
11
+ * renders, never the full set and never the package's ~3000-icon barrel
12
+ * (importing that barrel makes Vite transform all 3000 modules on the
13
+ * first dev load, which dominated cold start).
14
+ *
15
+ * Because the import is dynamic, a brand icon isn't available during SSR;
16
+ * `BrandIcon` renders a sized placeholder until the module resolves on the
17
+ * client. The placeholder is identical on the server and the first client
18
+ * render, so hydration never mismatches. Langs without a brand icon fall
19
+ * back to a semantic lucide id (string) handled by `resolveIcon`.
20
+ */
21
+
22
+ // canonical lang → loader (() => import of that icon's module).
23
+ const BRAND_LOADERS = {
24
+ javascript: () => import('@icons-pack/react-simple-icons/icons/SiJavascript.mjs'),
25
+ typescript: () => import('@icons-pack/react-simple-icons/icons/SiTypescript.mjs'),
26
+ jsx: () => import('@icons-pack/react-simple-icons/icons/SiReact.mjs'),
27
+ tsx: () => import('@icons-pack/react-simple-icons/icons/SiReact.mjs'),
28
+ python: () => import('@icons-pack/react-simple-icons/icons/SiPython.mjs'),
29
+ ruby: () => import('@icons-pack/react-simple-icons/icons/SiRuby.mjs'),
30
+ go: () => import('@icons-pack/react-simple-icons/icons/SiGo.mjs'),
31
+ rust: () => import('@icons-pack/react-simple-icons/icons/SiRust.mjs'),
32
+ java: () => import('@icons-pack/react-simple-icons/icons/SiOpenjdk.mjs'),
33
+ csharp: () => import('@icons-pack/react-simple-icons/icons/SiSharp.mjs'),
34
+ c: () => import('@icons-pack/react-simple-icons/icons/SiC.mjs'),
35
+ cpp: () => import('@icons-pack/react-simple-icons/icons/SiCplusplus.mjs'),
36
+ php: () => import('@icons-pack/react-simple-icons/icons/SiPhp.mjs'),
37
+ swift: () => import('@icons-pack/react-simple-icons/icons/SiSwift.mjs'),
38
+ kotlin: () => import('@icons-pack/react-simple-icons/icons/SiKotlin.mjs'),
39
+ scala: () => import('@icons-pack/react-simple-icons/icons/SiScala.mjs'),
40
+ dart: () => import('@icons-pack/react-simple-icons/icons/SiDart.mjs'),
41
+ r: () => import('@icons-pack/react-simple-icons/icons/SiR.mjs'),
42
+ haskell: () => import('@icons-pack/react-simple-icons/icons/SiHaskell.mjs'),
43
+ elixir: () => import('@icons-pack/react-simple-icons/icons/SiElixir.mjs'),
44
+ erlang: () => import('@icons-pack/react-simple-icons/icons/SiErlang.mjs'),
45
+ lua: () => import('@icons-pack/react-simple-icons/icons/SiLua.mjs'),
46
+ perl: () => import('@icons-pack/react-simple-icons/icons/SiPerl.mjs'),
47
+ julia: () => import('@icons-pack/react-simple-icons/icons/SiJulia.mjs'),
48
+ ocaml: () => import('@icons-pack/react-simple-icons/icons/SiOcaml.mjs'),
49
+ fsharp: () => import('@icons-pack/react-simple-icons/icons/SiFsharp.mjs'),
50
+ nim: () => import('@icons-pack/react-simple-icons/icons/SiNim.mjs'),
51
+ zig: () => import('@icons-pack/react-simple-icons/icons/SiZig.mjs'),
52
+ clojure: () => import('@icons-pack/react-simple-icons/icons/SiClojure.mjs'),
53
+ coffeescript: () => import('@icons-pack/react-simple-icons/icons/SiCoffeescript.mjs'),
54
+ html: () => import('@icons-pack/react-simple-icons/icons/SiHtml5.mjs'),
55
+ css: () => import('@icons-pack/react-simple-icons/icons/SiCss.mjs'),
56
+ vue: () => import('@icons-pack/react-simple-icons/icons/SiVuedotjs.mjs'),
57
+ svelte: () => import('@icons-pack/react-simple-icons/icons/SiSvelte.mjs'),
58
+ solid: () => import('@icons-pack/react-simple-icons/icons/SiSolid.mjs'),
59
+ json: () => import('@icons-pack/react-simple-icons/icons/SiJson.mjs'),
60
+ yaml: () => import('@icons-pack/react-simple-icons/icons/SiYaml.mjs'),
61
+ toml: () => import('@icons-pack/react-simple-icons/icons/SiToml.mjs'),
62
+ graphql: () => import('@icons-pack/react-simple-icons/icons/SiGraphql.mjs'),
63
+ markdown: () => import('@icons-pack/react-simple-icons/icons/SiMarkdown.mjs'),
64
+ mdx: () => import('@icons-pack/react-simple-icons/icons/SiMarkdown.mjs'),
65
+ latex: () => import('@icons-pack/react-simple-icons/icons/SiLatex.mjs'),
66
+ asciidoc: () => import('@icons-pack/react-simple-icons/icons/SiAsciidoctor.mjs'),
67
+ bash: () => import('@icons-pack/react-simple-icons/icons/SiGnubash.mjs'),
68
+ shell: () => import('@icons-pack/react-simple-icons/icons/SiGnubash.mjs'),
69
+ sh: () => import('@icons-pack/react-simple-icons/icons/SiGnubash.mjs'),
70
+ fish: () => import('@icons-pack/react-simple-icons/icons/SiFishshell.mjs'),
71
+ nodejs: () => import('@icons-pack/react-simple-icons/icons/SiNodedotjs.mjs'),
72
+ mysql: () => import('@icons-pack/react-simple-icons/icons/SiMysql.mjs'),
73
+ postgresql: () => import('@icons-pack/react-simple-icons/icons/SiPostgresql.mjs'),
74
+ plsql: () => import('@icons-pack/react-simple-icons/icons/SiPostgresql.mjs'),
75
+ sqlite: () => import('@icons-pack/react-simple-icons/icons/SiSqlite.mjs'),
76
+ mongodb: () => import('@icons-pack/react-simple-icons/icons/SiMongodb.mjs'),
77
+ redis: () => import('@icons-pack/react-simple-icons/icons/SiRedis.mjs'),
78
+ docker: () => import('@icons-pack/react-simple-icons/icons/SiDocker.mjs'),
79
+ dockerfile: () => import('@icons-pack/react-simple-icons/icons/SiDocker.mjs'),
80
+ kubernetes: () => import('@icons-pack/react-simple-icons/icons/SiKubernetes.mjs'),
81
+ nginx: () => import('@icons-pack/react-simple-icons/icons/SiNginx.mjs'),
82
+ apacheconf: () => import('@icons-pack/react-simple-icons/icons/SiApache.mjs'),
83
+ };
84
+
85
+ // Generic lucide fallback for langs without a brand icon. Returns a
86
+ // lucide id string — `resolveIcon` turns it into the icon component.
87
+ const LUCIDE = {
88
+ sql: 'database',
89
+ xml: 'code-xml',
90
+ diff: 'git-compare-arrows',
91
+ ini: 'braces',
92
+ properties: 'braces',
93
+ powershell: 'terminal',
94
+ batch: 'terminal',
95
+ zsh: 'terminal',
96
+ text: 'file-code',
97
+ plaintext: 'file-code',
98
+ };
99
+
100
+ // Module-level cache so an icon that's been loaded once renders
101
+ // synchronously everywhere after (no repeat import, no flash).
102
+ const iconCache = new Map();
103
+
104
+ /**
105
+ * Renders a brand icon loaded lazily by `key`. SSR + the first client
106
+ * render emit a sized placeholder (identical → hydration-safe); after
107
+ * mount the icon's module is imported and swapped in.
108
+ */
109
+ function BrandIcon({ iconKey, ...props }) {
110
+ const [Icon, setIcon] = useState(() => iconCache.get(iconKey) || null);
111
+
112
+ useEffect(() => {
113
+ if (iconCache.has(iconKey)) {
114
+ setIcon(() => iconCache.get(iconKey));
115
+ return;
116
+ }
117
+ const load = BRAND_LOADERS[iconKey];
118
+ if (!load) return;
119
+ let alive = true;
120
+ load().then((mod) => {
121
+ iconCache.set(iconKey, mod.default);
122
+ if (alive) setIcon(() => mod.default);
123
+ });
124
+ return () => {
125
+ alive = false;
126
+ };
127
+ }, [iconKey]);
128
+
129
+ if (!Icon) {
130
+ // Reserve the icon's box so swapping it in causes no layout shift.
131
+ return (
132
+ <span
133
+ aria-hidden="true"
134
+ style={{ display: 'inline-block', width: '1em', height: '1em' }}
135
+ />
136
+ );
137
+ }
138
+ return <Icon {...props} />;
139
+ }
140
+
141
+ export function iconForLang(lang) {
142
+ if (!lang) return 'file-code';
143
+ const id = resolveLang(lang) || lang;
144
+ const key = BRAND_LOADERS[id] ? id : BRAND_LOADERS[lang] ? lang : null;
145
+ if (key) return <BrandIcon iconKey={key} size="1em" />;
146
+ return LUCIDE[id] || LUCIDE[lang] || 'file-code';
147
+ }