@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,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
|
+
}
|