@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.
Files changed (90) hide show
  1. package/dist/cli.js +11 -0
  2. package/package.json +52 -0
  3. package/runtime/velu-ui/base.css +311 -0
  4. package/runtime/velu-ui/components/Accordion.jsx +64 -0
  5. package/runtime/velu-ui/components/ApiClient.jsx +121 -0
  6. package/runtime/velu-ui/components/ApiField.jsx +87 -0
  7. package/runtime/velu-ui/components/ApiPath.jsx +63 -0
  8. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -0
  9. package/runtime/velu-ui/components/AskBar.jsx +71 -0
  10. package/runtime/velu-ui/components/Callout.jsx +114 -0
  11. package/runtime/velu-ui/components/Card.jsx +131 -0
  12. package/runtime/velu-ui/components/Chatbot.jsx +596 -0
  13. package/runtime/velu-ui/components/CodeBlock.jsx +375 -0
  14. package/runtime/velu-ui/components/Columns.jsx +56 -0
  15. package/runtime/velu-ui/components/Field.jsx +81 -0
  16. package/runtime/velu-ui/components/Image.jsx +163 -0
  17. package/runtime/velu-ui/components/MethodBadge.jsx +31 -0
  18. package/runtime/velu-ui/components/NavSelect.jsx +108 -0
  19. package/runtime/velu-ui/components/PageFeedback.jsx +219 -0
  20. package/runtime/velu-ui/components/PageFooter.jsx +213 -0
  21. package/runtime/velu-ui/components/PageHeader.jsx +414 -0
  22. package/runtime/velu-ui/components/PageNav.jsx +77 -0
  23. package/runtime/velu-ui/components/PoweredBy.jsx +51 -0
  24. package/runtime/velu-ui/components/Prompt.jsx +115 -0
  25. package/runtime/velu-ui/components/Search.jsx +366 -0
  26. package/runtime/velu-ui/components/Sidebar.jsx +191 -0
  27. package/runtime/velu-ui/components/Steps.jsx +65 -0
  28. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -0
  29. package/runtime/velu-ui/components/Toc.jsx +537 -0
  30. package/runtime/velu-ui/components/TocBar.jsx +195 -0
  31. package/runtime/velu-ui/components/Tree.jsx +87 -0
  32. package/runtime/velu-ui/components/TryItBar.jsx +90 -0
  33. package/runtime/velu-ui/components/accordion.css +92 -0
  34. package/runtime/velu-ui/components/api.css +479 -0
  35. package/runtime/velu-ui/components/ask-bar.css +94 -0
  36. package/runtime/velu-ui/components/card.css +105 -0
  37. package/runtime/velu-ui/components/chatbot.css +617 -0
  38. package/runtime/velu-ui/components/code-block.css +263 -0
  39. package/runtime/velu-ui/components/docs-layout.css +775 -0
  40. package/runtime/velu-ui/components/field.css +82 -0
  41. package/runtime/velu-ui/components/image.css +237 -0
  42. package/runtime/velu-ui/components/nav-select.css +157 -0
  43. package/runtime/velu-ui/components/page-feedback.css +241 -0
  44. package/runtime/velu-ui/components/page-footer.css +130 -0
  45. package/runtime/velu-ui/components/page-header.css +520 -0
  46. package/runtime/velu-ui/components/page-nav.css +50 -0
  47. package/runtime/velu-ui/components/powered-by.css +66 -0
  48. package/runtime/velu-ui/components/prompt.css +99 -0
  49. package/runtime/velu-ui/components/search.css +307 -0
  50. package/runtime/velu-ui/components/sidebar.css +144 -0
  51. package/runtime/velu-ui/components/steps.css +77 -0
  52. package/runtime/velu-ui/components/theme-toggle.css +70 -0
  53. package/runtime/velu-ui/components/toc-bar.css +234 -0
  54. package/runtime/velu-ui/components/tree.css +49 -0
  55. package/runtime/velu-ui/index.js +45 -0
  56. package/runtime/velu-ui/lib/copyText.js +64 -0
  57. package/runtime/velu-ui/lib/lang-icons.jsx +156 -0
  58. package/runtime/velu-ui/lib/prism-langs.js +957 -0
  59. package/runtime/velu-ui/lib/prism-loader.js +74 -0
  60. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -0
  61. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -0
  62. package/runtime/velu-ui/mdx-components.jsx +85 -0
  63. package/runtime/velu-ui/primitives/Cluster.jsx +49 -0
  64. package/runtime/velu-ui/primitives/Stack.jsx +63 -0
  65. package/runtime/velu-ui/primitives/Switcher.jsx +57 -0
  66. package/runtime/velu-ui/primitives/stack.css +3 -0
  67. package/runtime/velu-ui/primitives/switcher.css +25 -0
  68. package/runtime/velu-ui/styles.css +43 -0
  69. package/runtime/velu-ui/tokens.css +4 -0
  70. package/schema/velu.schema.json +167 -0
  71. package/src/navigation.js +434 -0
  72. package/src/runtime/App.jsx +1473 -0
  73. package/src/runtime/client-entry.jsx +22 -0
  74. package/src/runtime/server-entry.jsx +16 -0
  75. package/src/template.html +48 -0
  76. package/templates/starter/ai-tools/claude-code.mdx +26 -0
  77. package/templates/starter/ai-tools/cursor.mdx +17 -0
  78. package/templates/starter/api-reference/endpoint/create.mdx +24 -0
  79. package/templates/starter/api-reference/endpoint/get.mdx +27 -0
  80. package/templates/starter/api-reference/introduction.mdx +28 -0
  81. package/templates/starter/development.mdx +19 -0
  82. package/templates/starter/essentials/code.mdx +28 -0
  83. package/templates/starter/essentials/images.mdx +29 -0
  84. package/templates/starter/essentials/markdown.mdx +25 -0
  85. package/templates/starter/essentials/navigation.mdx +39 -0
  86. package/templates/starter/essentials/settings.mdx +30 -0
  87. package/templates/starter/favicon.svg +6 -0
  88. package/templates/starter/index.mdx +31 -0
  89. package/templates/starter/quickstart.mdx +31 -0
  90. package/templates/starter/velu.json +33 -0
@@ -0,0 +1,234 @@
1
+ /* TocBar — narrow-layout counterpart to <Toc>.
2
+
3
+ Rendered always (so SSR / hydration markup matches at every size)
4
+ but VISIBLE only at narrow widths via a container query against the
5
+ docs layout. At wide widths the right-rail <Toc> takes over and
6
+ this bar is `display: none` — no media queries.
7
+
8
+ All values are tokens; light/dark via [data-theme]. */
9
+
10
+ .velu-toc-bar {
11
+ /* Hidden by default — the wide layout uses the right-rail <Toc>.
12
+ Container query below flips it on at narrow widths. */
13
+ display: none;
14
+ /* Sticky to the top of the article so it stays in view as the
15
+ user scrolls through the page. Sits below the page header. */
16
+ position: sticky;
17
+ inset-block-start: var(--velu-header-height);
18
+ z-index: 5;
19
+ /* Frosted-glass background — same recipe as the scrolled header
20
+ (.velu-header--scrolled in page-header.css). 75% --page-bg + a
21
+ saturate/blur backdrop lets article content show through faintly
22
+ so the bar reads as a layer over the page rather than a solid
23
+ band. The dropdown below uses the same recipe so the bar +
24
+ expanded list look like one continuous translucent fabric. */
25
+ background: color-mix(in srgb, var(--page-bg) 75%, transparent);
26
+ backdrop-filter: saturate(140%) blur(12px);
27
+ -webkit-backdrop-filter: saturate(140%) blur(12px);
28
+ border-block-end: var(--border-width) solid var(--surface-color);
29
+ /* base.css's global `* { max-width: 66ch }` (the prose measure)
30
+ would otherwise cap the bar AND its dropdown well short of the
31
+ viewport edge — explicitly override it for the bar's subtree. */
32
+ max-inline-size: none;
33
+ }
34
+ .velu-toc-bar *,
35
+ .velu-toc-bar {
36
+ max-inline-size: none;
37
+ }
38
+ /* When expanded, the dropdown's own border-block-end terminates
39
+ the combined bar+dropdown — suppress the bar's bottom border so
40
+ there isn't a double-printed line between bar and dropdown. The
41
+ toggle's `border-block-end` still draws the divider between the
42
+ toggle row and the list. Also drop the frosted recipe — the
43
+ dropdown reveals article content underneath, and a translucent
44
+ bar+dropdown would let the article text bleed through the menu.
45
+ Solid --page-bg keeps the list readable in either theme. */
46
+ .velu-toc-bar[data-expanded='true'] {
47
+ border-block-end-color: transparent;
48
+ background: var(--page-bg);
49
+ backdrop-filter: none;
50
+ -webkit-backdrop-filter: none;
51
+ }
52
+ .velu-toc-bar[data-expanded='true'] .velu-toc-bar__list {
53
+ background: var(--page-bg);
54
+ backdrop-filter: none;
55
+ -webkit-backdrop-filter: none;
56
+ }
57
+
58
+ @container docs (max-width: 1024px) {
59
+ .velu-toc-bar {
60
+ display: block;
61
+ }
62
+ }
63
+
64
+ /* ── Toggle (collapsed header) ─────────────────────────────────────── */
65
+ .velu-toc-bar__toggle {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: var(--s-2);
69
+ inline-size: 100%;
70
+ /* Block padding fixed at 8px so the bar hugs its single-row
71
+ content tightly; inline padding stays on the spacing scale. */
72
+ padding-block: 0.5rem;
73
+ padding-inline: var(--s3);
74
+ /* Divider between the toggle row and the expanded list lives
75
+ here (not on the list) so it spans the toggle's full inline
76
+ size — touching the sidebar's vertical border on the left
77
+ instead of being inset by the list's leading margin. Hidden
78
+ when collapsed (TocBar's own bottom border terminates the
79
+ row). */
80
+ border-width: 0 0 var(--border-width) 0;
81
+ border-style: solid;
82
+ border-color: var(--surface-color);
83
+ background: transparent;
84
+ color: var(--text-color);
85
+ font: inherit;
86
+ font-size: var(--f-h5);
87
+ text-align: start;
88
+ cursor: pointer;
89
+ }
90
+ /* No interior divider between the toggle row and the dropdown —
91
+ they're one continuous fabric. The only horizontal line in the
92
+ expanded state is the dropdown's own border-block-end, at the
93
+ very bottom of the combined element. */
94
+ .velu-toc-bar__toggle {
95
+ border-block-end-color: transparent;
96
+ }
97
+
98
+ /* Reading-progress ring — SVG circle whose stroke fills as the
99
+ reader scrolls. Sized like a chip so it reads as a peer of the
100
+ chevron at the other edge of the toggle row. */
101
+ .velu-toc-bar__progress {
102
+ flex: none;
103
+ inline-size: 1.25em;
104
+ block-size: 1.25em;
105
+ }
106
+
107
+ .velu-toc-bar__title {
108
+ flex: 1;
109
+ min-inline-size: 0;
110
+ }
111
+
112
+ .velu-toc-bar__chevron {
113
+ flex: none;
114
+ inline-size: var(--f-body);
115
+ block-size: var(--f-body);
116
+ color: var(--muted-color);
117
+ transition: transform 0.2s ease;
118
+ }
119
+ .velu-toc-bar[data-expanded='true'] .velu-toc-bar__chevron {
120
+ transform: rotate(180deg);
121
+ }
122
+
123
+ /* ── Expanded list (overlay) ───────────────────────────────────────── */
124
+ /* `position: absolute` overlay anchored to the bottom of the sticky
125
+ TocBar — the list does NOT participate in the article's normal
126
+ flow. That's the critical bit: when the user expands/collapses
127
+ the dropdown, the article does not reflow. Scroll targets
128
+ computed against the article (e.g. for a "scroll to this heading"
129
+ click) therefore remain valid regardless of dropdown state. */
130
+ .velu-toc-bar__list {
131
+ /* `absolute` (relative to the sticky TocBar root) so the dropdown
132
+ stays glued to the bar — no measurement gap between bar's bottom
133
+ and dropdown's top. The bar itself is full-width of the centre
134
+ column, which at narrow widths reaches the viewport's right edge
135
+ (centre's `margin-inline-end: 0` via @container), so the dropdown
136
+ also reaches the viewport right edge via `inset-inline-end: 0`. */
137
+ position: absolute;
138
+ inset-block-start: 100%;
139
+ inset-inline-start: 0;
140
+ inset-inline-end: 0;
141
+ margin: 0;
142
+ list-style: none;
143
+ /* Vertical breathing room around items. */
144
+ padding-block: var(--s-1);
145
+ /* Items are pushed --s3 off the bar's left edge so they sit at
146
+ the rail's x position (drawn by ::before below). */
147
+ padding-inline-start: var(--s3);
148
+ padding-inline-end: 0;
149
+ /* Collapsed state — invisible, lifted slightly upward and out
150
+ of pointer events. The `[data-expanded='true']` rule below
151
+ animates these back on open. The list is ALWAYS in the DOM
152
+ (not conditionally rendered) so the transition can run both
153
+ directions. `visibility` is delayed via the transition's
154
+ 0.18s tail so it switches AFTER the opacity fade completes
155
+ when closing. */
156
+ opacity: 0;
157
+ visibility: hidden;
158
+ transform: translateY(-0.25rem);
159
+ transform-origin: 0 0;
160
+ pointer-events: none;
161
+ transition:
162
+ opacity 0.18s ease,
163
+ transform 0.18s ease,
164
+ visibility 0s linear 0.18s;
165
+ /* Same frosted recipe as the bar above (no shadow, no top
166
+ border) so the open dropdown reads as the bar EXTENDED DOWN,
167
+ not a separate floating panel. */
168
+ background: color-mix(in srgb, var(--page-bg) 75%, transparent);
169
+ backdrop-filter: saturate(140%) blur(12px);
170
+ -webkit-backdrop-filter: saturate(140%) blur(12px);
171
+ /* Bottom border terminates the combined bar+dropdown against
172
+ the article below. Spans the FULL list width so it touches
173
+ the sidebar's vertical border on the left. */
174
+ border-block-end: var(--border-width) solid var(--surface-color);
175
+ /* Cap height so a long TOC scrolls INSIDE the dropdown rather
176
+ than overflowing the viewport. The active row is auto-scrolled
177
+ into view by `scrollIntoNearestView` in TocBar.jsx. */
178
+ max-block-size: min(
179
+ 75vh,
180
+ calc(100vh - var(--velu-scroll-offset, 5rem) - 2rem)
181
+ );
182
+ overflow-y: auto;
183
+ overscroll-behavior: contain;
184
+ /* Above the article but below sticky chrome above it. */
185
+ z-index: 4;
186
+ }
187
+
188
+ /* Expanded state — fade + slide into view. The opacity / transform
189
+ transitions run forward; visibility flips to `visible` instantly
190
+ so the dropdown is interactive immediately when opening. */
191
+ .velu-toc-bar[data-expanded='true'] .velu-toc-bar__list {
192
+ opacity: 1;
193
+ visibility: visible;
194
+ transform: translateY(0);
195
+ pointer-events: auto;
196
+ transition:
197
+ opacity 0.18s ease,
198
+ transform 0.18s ease,
199
+ visibility 0s;
200
+ }
201
+
202
+ /* Vertical guide rail — inset by --s3 from the list's leading edge,
203
+ tall enough to span the items (between the padding-block bands).
204
+ Active item's 2px accent border (below) overlays it at its row. */
205
+ .velu-toc-bar__list::before {
206
+ content: '';
207
+ position: absolute;
208
+ inset-block-start: var(--s-1);
209
+ inset-block-end: var(--s-1);
210
+ inset-inline-start: var(--s3);
211
+ inline-size: 1px;
212
+ background: var(--border-color);
213
+ }
214
+
215
+ .velu-toc-bar__item {
216
+ /* The list's own `padding-inline-start: var(--s3)` already places
217
+ each item's leading edge AT the rail's x position, so the
218
+ active item's 2px accent border overlays the rail at its row
219
+ with no negative-margin tricks. */
220
+ padding-block: var(--s-2);
221
+ padding-inline-end: var(--s3);
222
+ border-inline-start: 2px solid transparent;
223
+ color: var(--text-color);
224
+ font-size: var(--f-h6);
225
+ cursor: pointer;
226
+ transition: color 0.12s ease, border-color 0.12s ease;
227
+ }
228
+ .velu-toc-bar__item:hover {
229
+ color: var(--accent-color);
230
+ }
231
+ .velu-toc-bar__item--active {
232
+ color: var(--accent-color);
233
+ border-inline-start-color: var(--accent-color);
234
+ }
@@ -0,0 +1,49 @@
1
+ /* Tree — file-tree display. Monospace + small line-height for the
2
+ "code editor sidebar" look. All values are tokens; light/dark via
3
+ [data-theme]. */
4
+
5
+ .velu-tree {
6
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
7
+ font-size: var(--f-body);
8
+ line-height: 1.6;
9
+ color: var(--text-color);
10
+ }
11
+
12
+ .velu-tree__item {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: var(--s-1);
16
+ padding-block: var(--s-4);
17
+ }
18
+
19
+ /* Folder summary acts as the click target — strip the native
20
+ disclosure triangle and make the whole row look like a button. */
21
+ .velu-tree__item--folder {
22
+ cursor: pointer;
23
+ list-style: none;
24
+ }
25
+ .velu-tree__item--folder::-webkit-details-marker {
26
+ display: none;
27
+ }
28
+
29
+ .velu-tree__icon {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ flex: none;
33
+ inline-size: var(--icon-size-lg);
34
+ block-size: var(--icon-size-lg);
35
+ }
36
+
37
+ .velu-tree__label {
38
+ font-family: inherit;
39
+ }
40
+
41
+ /* Nested children sit inside a div with a vertical rail (border-inline-
42
+ start) — that's the line connecting a folder's siblings. The
43
+ margin/padding offset places the rail roughly under the parent
44
+ folder icon's horizontal center. */
45
+ .velu-tree__nest {
46
+ margin-inline-start: 0.625em;
47
+ padding-inline-start: 1em;
48
+ border-inline-start: var(--border-width) solid var(--border-color);
49
+ }
@@ -0,0 +1,45 @@
1
+ export { default as Stack } from './primitives/Stack.jsx';
2
+ export { default as Switcher } from './primitives/Switcher.jsx';
3
+ export { default as Cluster } from './primitives/Cluster.jsx';
4
+ export { default as ThemeToggle } from './components/ThemeToggle.jsx';
5
+ export { default as Sidebar } from './components/Sidebar.jsx';
6
+ export { default as NavSelect } from './components/NavSelect.jsx';
7
+ export { default as Toc } from './components/Toc.jsx';
8
+ export { default as TocBar } from './components/TocBar.jsx';
9
+ export { default as Callout } from './components/Callout.jsx';
10
+ export {
11
+ default as Accordion,
12
+ AccordionGroup,
13
+ } from './components/Accordion.jsx';
14
+ export { default as Card, CardGroup } from './components/Card.jsx';
15
+ export { default as Columns } from './components/Columns.jsx';
16
+ export { default as Field } from './components/Field.jsx';
17
+ export { default as Prompt } from './components/Prompt.jsx';
18
+ export { default as Steps, Step } from './components/Steps.jsx';
19
+ export { default as Tree, Folder, File } from './components/Tree.jsx';
20
+ export { default as MethodBadge } from './components/MethodBadge.jsx';
21
+ export { default as ApiPath } from './components/ApiPath.jsx';
22
+ export { default as TryItBar } from './components/TryItBar.jsx';
23
+ export { default as ApiField } from './components/ApiField.jsx';
24
+ export { default as ApiClient } from './components/ApiClient.jsx';
25
+ export { default as ApiSidebar } from './components/ApiSidebar.jsx';
26
+ export { default as AskBar } from './components/AskBar.jsx';
27
+ export { default as Chatbot } from './components/Chatbot.jsx';
28
+ export { default as PageFeedback } from './components/PageFeedback.jsx';
29
+ export { default as PageNav } from './components/PageNav.jsx';
30
+ export { default as PageFooter } from './components/PageFooter.jsx';
31
+ export { default as PoweredBy } from './components/PoweredBy.jsx';
32
+ export { default as resolveIcon } from './lib/resolveIcon.jsx';
33
+ export {
34
+ default as PageHeader,
35
+ VeluMark,
36
+ } from './components/PageHeader.jsx';
37
+ export { default as Search } from './components/Search.jsx';
38
+ export { default as Image } from './components/Image.jsx';
39
+ export {
40
+ default as CodeBlock,
41
+ CodeGroup,
42
+ } from './components/CodeBlock.jsx';
43
+ export {
44
+ default as defaultMdxComponents,
45
+ } from './mdx-components.jsx';
@@ -0,0 +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>:5173 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
+ }
@@ -0,0 +1,156 @@
1
+ import React from 'react';
2
+ import {
3
+ SiJavascript,
4
+ SiTypescript,
5
+ SiReact,
6
+ SiPython,
7
+ SiRuby,
8
+ SiGo,
9
+ SiRust,
10
+ SiOpenjdk,
11
+ SiSharp,
12
+ SiC,
13
+ SiCplusplus,
14
+ SiPhp,
15
+ SiSwift,
16
+ SiKotlin,
17
+ SiScala,
18
+ SiDart,
19
+ SiR,
20
+ SiHaskell,
21
+ SiElixir,
22
+ SiErlang,
23
+ SiLua,
24
+ SiPerl,
25
+ SiJulia,
26
+ SiOcaml,
27
+ SiFsharp,
28
+ SiNim,
29
+ SiZig,
30
+ SiClojure,
31
+ SiCoffeescript,
32
+ SiHtml5,
33
+ SiCss,
34
+ SiVuedotjs,
35
+ SiSvelte,
36
+ SiSolid,
37
+ SiJson,
38
+ SiYaml,
39
+ SiToml,
40
+ SiGraphql,
41
+ SiMarkdown,
42
+ SiLatex,
43
+ SiAsciidoctor,
44
+ SiGnubash,
45
+ SiFishshell,
46
+ SiNodedotjs,
47
+ SiMysql,
48
+ SiPostgresql,
49
+ SiSqlite,
50
+ SiMongodb,
51
+ SiRedis,
52
+ SiDocker,
53
+ SiKubernetes,
54
+ SiNginx,
55
+ SiApache,
56
+ } from '@icons-pack/react-simple-icons';
57
+ import { resolveLang } from './prism-loader.js';
58
+
59
+ /**
60
+ * Language → icon mapping.
61
+ *
62
+ * Brand icons (JS / Python / Ruby / etc.) come from
63
+ * @icons-pack/react-simple-icons — monochrome SVG, tree-shaken, scales to
64
+ * the surrounding font size via `size="1em"`. Anything without a brand
65
+ * icon falls back to a semantic lucide id (string) which `resolveIcon`
66
+ * handles downstream.
67
+ *
68
+ * Tab's icon prop already accepts either a React node or a lucide id, so
69
+ * we can mix both freely here.
70
+ */
71
+
72
+ // Brand icons (React components from simple-icons).
73
+ const BRAND = {
74
+ javascript: SiJavascript,
75
+ typescript: SiTypescript,
76
+ jsx: SiReact,
77
+ tsx: SiReact,
78
+ python: SiPython,
79
+ ruby: SiRuby,
80
+ go: SiGo,
81
+ rust: SiRust,
82
+ java: SiOpenjdk,
83
+ csharp: SiSharp,
84
+ c: SiC,
85
+ cpp: SiCplusplus,
86
+ php: SiPhp,
87
+ swift: SiSwift,
88
+ kotlin: SiKotlin,
89
+ scala: SiScala,
90
+ dart: SiDart,
91
+ r: SiR,
92
+ haskell: SiHaskell,
93
+ elixir: SiElixir,
94
+ erlang: SiErlang,
95
+ lua: SiLua,
96
+ perl: SiPerl,
97
+ julia: SiJulia,
98
+ ocaml: SiOcaml,
99
+ fsharp: SiFsharp,
100
+ nim: SiNim,
101
+ zig: SiZig,
102
+ clojure: SiClojure,
103
+ coffeescript: SiCoffeescript,
104
+ html: SiHtml5,
105
+ css: SiCss,
106
+ vue: SiVuedotjs,
107
+ svelte: SiSvelte,
108
+ solid: SiSolid,
109
+ json: SiJson,
110
+ yaml: SiYaml,
111
+ toml: SiToml,
112
+ graphql: SiGraphql,
113
+ markdown: SiMarkdown,
114
+ mdx: SiMarkdown,
115
+ latex: SiLatex,
116
+ asciidoc: SiAsciidoctor,
117
+ bash: SiGnubash,
118
+ shell: SiGnubash,
119
+ sh: SiGnubash,
120
+ fish: SiFishshell,
121
+ nodejs: SiNodedotjs,
122
+ mysql: SiMysql,
123
+ postgresql: SiPostgresql,
124
+ plsql: SiPostgresql,
125
+ sqlite: SiSqlite,
126
+ mongodb: SiMongodb,
127
+ redis: SiRedis,
128
+ docker: SiDocker,
129
+ dockerfile: SiDocker,
130
+ kubernetes: SiKubernetes,
131
+ nginx: SiNginx,
132
+ apacheconf: SiApache,
133
+ };
134
+
135
+ // Generic lucide fallback for langs without a brand icon. Returns a
136
+ // lucide id string — `resolveIcon` turns it into the icon component.
137
+ const LUCIDE = {
138
+ sql: 'database',
139
+ xml: 'code-xml',
140
+ diff: 'git-compare-arrows',
141
+ ini: 'braces',
142
+ properties: 'braces',
143
+ powershell: 'terminal',
144
+ batch: 'terminal',
145
+ zsh: 'terminal',
146
+ text: 'file-code',
147
+ plaintext: 'file-code',
148
+ };
149
+
150
+ export function iconForLang(lang) {
151
+ if (!lang) return 'file-code';
152
+ const id = resolveLang(lang) || lang;
153
+ const Cmp = BRAND[id] || BRAND[lang];
154
+ if (Cmp) return <Cmp size="1em" />;
155
+ return LUCIDE[id] || LUCIDE[lang] || 'file-code';
156
+ }