boltdocs 1.3.1 → 1.3.3
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/CodeBlock-QYIKJMEB.mjs +7 -0
- package/dist/PackageManagerTabs-XW3AVXVX.mjs +99 -0
- package/dist/{SearchDialog-5EDRACEG.mjs → SearchDialog-FBNGKRPK.mjs} +2 -1
- package/dist/{SearchDialog-X57WPTNN.css → SearchDialog-O3V36MXA.css} +32 -27
- package/dist/{cache-EHR7SXRU.mjs → cache-GQHF6BXI.mjs} +1 -1
- package/dist/{chunk-GSYECEZY.mjs → chunk-CYBWLFOG.mjs} +5 -1
- package/dist/{chunk-NS7WHDYA.mjs → chunk-D7YBQG6H.mjs} +7 -20
- package/dist/chunk-FMTOYQLO.mjs +37 -0
- package/dist/chunk-KS5B3O6W.mjs +43 -0
- package/dist/{PackageManagerTabs-XEKI3L7P.mjs → chunk-S5G55FBI.mjs} +4 -105
- package/dist/client/index.css +32 -27
- package/dist/client/index.js +113 -88
- package/dist/client/index.mjs +73 -31
- package/dist/client/ssr.css +32 -27
- package/dist/client/ssr.js +44 -59
- package/dist/client/ssr.mjs +2 -1
- package/dist/node/index.js +15 -14
- package/dist/node/index.mjs +13 -16
- package/package.json +1 -1
- package/src/client/index.ts +1 -0
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +11 -36
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +12 -35
- package/src/client/theme/components/mdx/Tabs.tsx +36 -4
- package/src/client/theme/components/mdx/mdx-components.css +8 -0
- package/src/client/theme/icons/yarn.tsx +16 -0
- package/src/client/theme/styles/markdown.css +26 -29
- package/src/client/utils.ts +23 -0
- package/src/node/ssg/meta.ts +11 -12
- package/src/node/ssg/sitemap.ts +2 -1
- package/src/node/utils.ts +11 -0
- package/tsconfig.json +1 -1
- package/dist/CodeBlock-V3Z5EKGR.mjs +0 -6
- package/dist/chunk-2YRDWM6O.mjs +0 -56
package/dist/client/ssr.js
CHANGED
|
@@ -611,7 +611,7 @@ async function getStarsRepo(repo) {
|
|
|
611
611
|
return "0";
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
|
-
var formatStars;
|
|
614
|
+
var formatStars, copyToClipboard;
|
|
615
615
|
var init_utils = __esm({
|
|
616
616
|
"src/client/utils.ts"() {
|
|
617
617
|
"use strict";
|
|
@@ -621,6 +621,22 @@ var init_utils = __esm({
|
|
|
621
621
|
compactDisplay: "short"
|
|
622
622
|
}).format(count);
|
|
623
623
|
};
|
|
624
|
+
copyToClipboard = async (text) => {
|
|
625
|
+
try {
|
|
626
|
+
await navigator.clipboard.writeText(text);
|
|
627
|
+
return true;
|
|
628
|
+
} catch {
|
|
629
|
+
const textarea = document.createElement("textarea");
|
|
630
|
+
textarea.value = text;
|
|
631
|
+
textarea.style.position = "fixed";
|
|
632
|
+
textarea.style.opacity = "0";
|
|
633
|
+
document.body.appendChild(textarea);
|
|
634
|
+
textarea.select();
|
|
635
|
+
document.execCommand("copy");
|
|
636
|
+
document.body.removeChild(textarea);
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
624
640
|
}
|
|
625
641
|
});
|
|
626
642
|
|
|
@@ -1703,37 +1719,20 @@ function CodeBlock({ children, ...props }) {
|
|
|
1703
1719
|
}
|
|
1704
1720
|
const handleCopy = (0, import_react13.useCallback)(async () => {
|
|
1705
1721
|
const code = preRef.current?.textContent || "";
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
1710
|
-
} catch {
|
|
1711
|
-
const textarea = document.createElement("textarea");
|
|
1712
|
-
textarea.value = code;
|
|
1713
|
-
textarea.style.position = "fixed";
|
|
1714
|
-
textarea.style.opacity = "0";
|
|
1715
|
-
document.body.appendChild(textarea);
|
|
1716
|
-
textarea.select();
|
|
1717
|
-
document.execCommand("copy");
|
|
1718
|
-
document.body.removeChild(textarea);
|
|
1719
|
-
setCopied(true);
|
|
1720
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
1721
|
-
}
|
|
1722
|
+
copyToClipboard(code);
|
|
1723
|
+
setCopied(true);
|
|
1724
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1722
1725
|
}, []);
|
|
1723
1726
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "code-block-wrapper", children: [
|
|
1724
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
"
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
children: copied ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Check, { size: 20 }) }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Copy, { size: 20 }) })
|
|
1734
|
-
}
|
|
1735
|
-
)
|
|
1736
|
-
] }),
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
1728
|
+
"button",
|
|
1729
|
+
{
|
|
1730
|
+
className: `code-block-copy ${copied ? "copied" : ""}`,
|
|
1731
|
+
onClick: handleCopy,
|
|
1732
|
+
"aria-label": "Copy code",
|
|
1733
|
+
children: copied ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.Copy, { size: 16 })
|
|
1734
|
+
}
|
|
1735
|
+
),
|
|
1737
1736
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("pre", { ref: preRef, ...props, children })
|
|
1738
1737
|
] });
|
|
1739
1738
|
}
|
|
@@ -1743,6 +1742,7 @@ var init_CodeBlock = __esm({
|
|
|
1743
1742
|
"use strict";
|
|
1744
1743
|
import_react13 = __toESM(require("react"));
|
|
1745
1744
|
import_lucide_react12 = require("lucide-react");
|
|
1745
|
+
init_utils();
|
|
1746
1746
|
import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1747
1747
|
}
|
|
1748
1748
|
});
|
|
@@ -2099,22 +2099,9 @@ function PackageManagerTabs({
|
|
|
2099
2099
|
const [copied, setCopied] = (0, import_react15.useState)(false);
|
|
2100
2100
|
const activeCommand = getCommandForManager(activeTab, command, pkg);
|
|
2101
2101
|
const handleCopy = (0, import_react15.useCallback)(async () => {
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
2106
|
-
} catch {
|
|
2107
|
-
const textarea = document.createElement("textarea");
|
|
2108
|
-
textarea.value = activeCommand;
|
|
2109
|
-
textarea.style.position = "fixed";
|
|
2110
|
-
textarea.style.opacity = "0";
|
|
2111
|
-
document.body.appendChild(textarea);
|
|
2112
|
-
textarea.select();
|
|
2113
|
-
document.execCommand("copy");
|
|
2114
|
-
document.body.removeChild(textarea);
|
|
2115
|
-
setCopied(true);
|
|
2116
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
2117
|
-
}
|
|
2102
|
+
copyToClipboard(activeCommand);
|
|
2103
|
+
setCopied(true);
|
|
2104
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
2118
2105
|
}, [activeCommand]);
|
|
2119
2106
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: `pkg-tabs-wrapper ${className}`, children: [
|
|
2120
2107
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "pkg-tabs-header", children: MANAGERS.map((mgr) => {
|
|
@@ -2136,19 +2123,16 @@ function PackageManagerTabs({
|
|
|
2136
2123
|
);
|
|
2137
2124
|
}) }),
|
|
2138
2125
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "code-block-wrapper pkg-tabs-content", children: [
|
|
2139
|
-
/* @__PURE__ */ (0, import_jsx_runtime26.
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
"
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
}
|
|
2150
|
-
)
|
|
2151
|
-
] }),
|
|
2126
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
2127
|
+
"button",
|
|
2128
|
+
{
|
|
2129
|
+
className: `code-block-copy ${copied ? "copied" : ""}`,
|
|
2130
|
+
onClick: handleCopy,
|
|
2131
|
+
type: "button",
|
|
2132
|
+
"aria-label": "Copy code",
|
|
2133
|
+
children: copied ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.Check, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react13.Copy, { size: 14 })
|
|
2134
|
+
}
|
|
2135
|
+
),
|
|
2152
2136
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("code", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "line", children: activeCommand }) }) })
|
|
2153
2137
|
] })
|
|
2154
2138
|
] });
|
|
@@ -2163,6 +2147,7 @@ var init_PackageManagerTabs = __esm({
|
|
|
2163
2147
|
init_pnpm();
|
|
2164
2148
|
init_bun();
|
|
2165
2149
|
init_deno();
|
|
2150
|
+
init_utils();
|
|
2166
2151
|
import_jsx_runtime26 = require("react/jsx-runtime");
|
|
2167
2152
|
MANAGERS = [
|
|
2168
2153
|
{ id: "npm", label: "npm", icon: NPM },
|
package/dist/client/ssr.mjs
CHANGED
package/dist/node/index.js
CHANGED
|
@@ -57,7 +57,10 @@ function parseFrontmatter(filePath) {
|
|
|
57
57
|
return { data, content };
|
|
58
58
|
}
|
|
59
59
|
function escapeHtml(str) {
|
|
60
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
60
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
61
|
+
}
|
|
62
|
+
function escapeXml(str) {
|
|
63
|
+
return escapeHtml(str);
|
|
61
64
|
}
|
|
62
65
|
function fileToRoutePath(relativePath) {
|
|
63
66
|
let cleanedPath = relativePath.split("/").map(stripNumberPrefix).join("/");
|
|
@@ -722,26 +725,24 @@ var import_url2 = require("url");
|
|
|
722
725
|
var import_module = require("module");
|
|
723
726
|
|
|
724
727
|
// src/node/ssg/meta.ts
|
|
728
|
+
init_utils();
|
|
725
729
|
function replaceMetaTags(html, meta) {
|
|
726
|
-
|
|
730
|
+
const title = escapeHtml(meta.title);
|
|
731
|
+
const description = escapeHtml(meta.description);
|
|
732
|
+
return html.replace(/<title>.*?<\/title>/, `<title>${title}</title>`).replace(
|
|
727
733
|
/(<meta name="description" content=")[^"]*(")/,
|
|
728
|
-
`$1${
|
|
729
|
-
).replace(
|
|
730
|
-
/(<meta property="og:title" content=")[^"]*(")/,
|
|
731
|
-
`$1${meta.title}$2`
|
|
732
|
-
).replace(
|
|
734
|
+
`$1${description}$2`
|
|
735
|
+
).replace(/(<meta property="og:title" content=")[^"]*(")/, `$1${title}$2`).replace(
|
|
733
736
|
/(<meta property="og:description" content=")[^"]*(")/,
|
|
734
|
-
`$1${
|
|
735
|
-
).replace(
|
|
736
|
-
/(<meta name="twitter:title" content=")[^"]*(")/,
|
|
737
|
-
`$1${meta.title}$2`
|
|
738
|
-
).replace(
|
|
737
|
+
`$1${description}$2`
|
|
738
|
+
).replace(/(<meta name="twitter:title" content=")[^"]*(")/, `$1${title}$2`).replace(
|
|
739
739
|
/(<meta name="twitter:description" content=")[^"]*(")/,
|
|
740
|
-
`$1${
|
|
740
|
+
`$1${description}$2`
|
|
741
741
|
);
|
|
742
742
|
}
|
|
743
743
|
|
|
744
744
|
// src/node/ssg/sitemap.ts
|
|
745
|
+
init_utils();
|
|
745
746
|
function generateSitemap(routePaths, config) {
|
|
746
747
|
const baseUrl = config?.siteUrl?.replace(/\/$/, "") || "https://example.com";
|
|
747
748
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -770,7 +771,7 @@ function generateSitemap(routePaths, config) {
|
|
|
770
771
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
771
772
|
${entries.map(
|
|
772
773
|
(e) => ` <url>
|
|
773
|
-
<loc>${baseUrl}${e.url}</loc>
|
|
774
|
+
<loc>${escapeXml(baseUrl)}${escapeXml(e.url)}</loc>
|
|
774
775
|
<lastmod>${today}</lastmod>
|
|
775
776
|
<changefreq>${e.changefreq}</changefreq>
|
|
776
777
|
<priority>${e.priority}</priority>
|
package/dist/node/index.mjs
CHANGED
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
TransformCache,
|
|
4
4
|
capitalize,
|
|
5
5
|
escapeHtml,
|
|
6
|
+
escapeXml,
|
|
6
7
|
extractNumberPrefix,
|
|
7
8
|
fileToRoutePath,
|
|
8
9
|
isDocFile,
|
|
9
10
|
normalizePath,
|
|
10
11
|
parseFrontmatter,
|
|
11
12
|
stripNumberPrefix
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-CYBWLFOG.mjs";
|
|
13
14
|
|
|
14
15
|
// src/node/plugin/index.ts
|
|
15
16
|
import { loadEnv } from "vite";
|
|
@@ -307,21 +308,17 @@ import { createRequire } from "module";
|
|
|
307
308
|
|
|
308
309
|
// src/node/ssg/meta.ts
|
|
309
310
|
function replaceMetaTags(html, meta) {
|
|
310
|
-
|
|
311
|
+
const title = escapeHtml(meta.title);
|
|
312
|
+
const description = escapeHtml(meta.description);
|
|
313
|
+
return html.replace(/<title>.*?<\/title>/, `<title>${title}</title>`).replace(
|
|
311
314
|
/(<meta name="description" content=")[^"]*(")/,
|
|
312
|
-
`$1${
|
|
313
|
-
).replace(
|
|
314
|
-
/(<meta property="og:title" content=")[^"]*(")/,
|
|
315
|
-
`$1${meta.title}$2`
|
|
316
|
-
).replace(
|
|
315
|
+
`$1${description}$2`
|
|
316
|
+
).replace(/(<meta property="og:title" content=")[^"]*(")/, `$1${title}$2`).replace(
|
|
317
317
|
/(<meta property="og:description" content=")[^"]*(")/,
|
|
318
|
-
`$1${
|
|
319
|
-
).replace(
|
|
320
|
-
/(<meta name="twitter:title" content=")[^"]*(")/,
|
|
321
|
-
`$1${meta.title}$2`
|
|
322
|
-
).replace(
|
|
318
|
+
`$1${description}$2`
|
|
319
|
+
).replace(/(<meta name="twitter:title" content=")[^"]*(")/, `$1${title}$2`).replace(
|
|
323
320
|
/(<meta name="twitter:description" content=")[^"]*(")/,
|
|
324
|
-
`$1${
|
|
321
|
+
`$1${description}$2`
|
|
325
322
|
);
|
|
326
323
|
}
|
|
327
324
|
|
|
@@ -354,7 +351,7 @@ function generateSitemap(routePaths, config) {
|
|
|
354
351
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
355
352
|
${entries.map(
|
|
356
353
|
(e) => ` <url>
|
|
357
|
-
<loc>${baseUrl}${e.url}</loc>
|
|
354
|
+
<loc>${escapeXml(baseUrl)}${escapeXml(e.url)}</loc>
|
|
358
355
|
<lastmod>${today}</lastmod>
|
|
359
356
|
<changefreq>${e.changefreq}</changefreq>
|
|
360
357
|
<priority>${e.priority}</priority>
|
|
@@ -433,7 +430,7 @@ async function generateStaticPages(options) {
|
|
|
433
430
|
console.log(
|
|
434
431
|
`[boltdocs] Generated ${routes.length} static pages + sitemap.xml`
|
|
435
432
|
);
|
|
436
|
-
const { flushCache } = await import("../cache-
|
|
433
|
+
const { flushCache } = await import("../cache-GQHF6BXI.mjs");
|
|
437
434
|
await flushCache();
|
|
438
435
|
}
|
|
439
436
|
|
|
@@ -615,7 +612,7 @@ function boltdocsPlugin(options = {}, passedConfig) {
|
|
|
615
612
|
if (!isBuild) return;
|
|
616
613
|
const outDir = viteConfig?.build?.outDir ? path4.resolve(viteConfig.root, viteConfig.build.outDir) : path4.resolve(process.cwd(), "dist");
|
|
617
614
|
await generateStaticPages({ docsDir, outDir, config });
|
|
618
|
-
const { flushCache } = await import("../cache-
|
|
615
|
+
const { flushCache } = await import("../cache-GQHF6BXI.mjs");
|
|
619
616
|
await flushCache();
|
|
620
617
|
}
|
|
621
618
|
},
|
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { BoltdocsConfig, BoltdocsThemeConfig } from "../node/config";
|
|
2
2
|
export type { ComponentRoute, CreateBoltdocsAppOptions } from "./types";
|
|
3
|
+
import { PackageManagerTabs } from "./theme/components/PackageManagerTabs";
|
|
3
4
|
export { createBoltdocsApp } from "./app";
|
|
4
5
|
export { ThemeLayout } from "./theme/ui/Layout";
|
|
5
6
|
export { Navbar } from "./theme/ui/Navbar";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useCallback } from "react";
|
|
2
2
|
import { Copy, Check } from "lucide-react";
|
|
3
|
+
import { copyToClipboard } from "../../../utils";
|
|
3
4
|
|
|
4
5
|
interface CodeBlockProps {
|
|
5
6
|
children?: React.ReactNode;
|
|
@@ -28,46 +29,20 @@ export function CodeBlock({ children, ...props }: CodeBlockProps) {
|
|
|
28
29
|
|
|
29
30
|
const handleCopy = useCallback(async () => {
|
|
30
31
|
const code = preRef.current?.textContent || "";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
setTimeout(() => setCopied(false), 2000);
|
|
35
|
-
} catch {
|
|
36
|
-
// Fallback
|
|
37
|
-
const textarea = document.createElement("textarea");
|
|
38
|
-
textarea.value = code;
|
|
39
|
-
textarea.style.position = "fixed";
|
|
40
|
-
textarea.style.opacity = "0";
|
|
41
|
-
document.body.appendChild(textarea);
|
|
42
|
-
textarea.select();
|
|
43
|
-
document.execCommand("copy");
|
|
44
|
-
document.body.removeChild(textarea);
|
|
45
|
-
setCopied(true);
|
|
46
|
-
setTimeout(() => setCopied(false), 2000);
|
|
47
|
-
}
|
|
32
|
+
copyToClipboard(code);
|
|
33
|
+
setCopied(true);
|
|
34
|
+
setTimeout(() => setCopied(false), 2000);
|
|
48
35
|
}, []);
|
|
49
36
|
|
|
50
37
|
return (
|
|
51
38
|
<div className="code-block-wrapper">
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
>
|
|
60
|
-
{copied ? (
|
|
61
|
-
<>
|
|
62
|
-
<Check size={20} />
|
|
63
|
-
</>
|
|
64
|
-
) : (
|
|
65
|
-
<>
|
|
66
|
-
<Copy size={20} />
|
|
67
|
-
</>
|
|
68
|
-
)}
|
|
69
|
-
</button>
|
|
70
|
-
</div>
|
|
39
|
+
<button
|
|
40
|
+
className={`code-block-copy ${copied ? "copied" : ""}`}
|
|
41
|
+
onClick={handleCopy}
|
|
42
|
+
aria-label="Copy code"
|
|
43
|
+
>
|
|
44
|
+
{copied ? <Check size={16} /> : <Copy size={16} />}
|
|
45
|
+
</button>
|
|
71
46
|
<pre ref={preRef} {...props}>
|
|
72
47
|
{children}
|
|
73
48
|
</pre>
|
|
@@ -4,6 +4,7 @@ import { NPM } from "../../icons/npm";
|
|
|
4
4
|
import { Pnpm } from "../../icons/pnpm";
|
|
5
5
|
import { Bun } from "../../icons/bun";
|
|
6
6
|
import { Deno } from "../../icons/deno";
|
|
7
|
+
import { copyToClipboard } from "../../../utils";
|
|
7
8
|
|
|
8
9
|
interface PackageManagerTabsProps {
|
|
9
10
|
command: string;
|
|
@@ -82,22 +83,9 @@ export function PackageManagerTabs({
|
|
|
82
83
|
const activeCommand = getCommandForManager(activeTab, command, pkg);
|
|
83
84
|
|
|
84
85
|
const handleCopy = useCallback(async () => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
setTimeout(() => setCopied(false), 2000);
|
|
89
|
-
} catch {
|
|
90
|
-
const textarea = document.createElement("textarea");
|
|
91
|
-
textarea.value = activeCommand;
|
|
92
|
-
textarea.style.position = "fixed";
|
|
93
|
-
textarea.style.opacity = "0";
|
|
94
|
-
document.body.appendChild(textarea);
|
|
95
|
-
textarea.select();
|
|
96
|
-
document.execCommand("copy");
|
|
97
|
-
document.body.removeChild(textarea);
|
|
98
|
-
setCopied(true);
|
|
99
|
-
setTimeout(() => setCopied(false), 2000);
|
|
100
|
-
}
|
|
86
|
+
copyToClipboard(activeCommand);
|
|
87
|
+
setCopied(true);
|
|
88
|
+
setTimeout(() => setCopied(false), 2000);
|
|
101
89
|
}, [activeCommand]);
|
|
102
90
|
|
|
103
91
|
return (
|
|
@@ -124,25 +112,14 @@ export function PackageManagerTabs({
|
|
|
124
112
|
|
|
125
113
|
{/* Code Block Content */}
|
|
126
114
|
<div className="code-block-wrapper pkg-tabs-content">
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{copied ? (
|
|
136
|
-
<>
|
|
137
|
-
<Check size={12} />
|
|
138
|
-
</>
|
|
139
|
-
) : (
|
|
140
|
-
<>
|
|
141
|
-
<Copy size={12} />
|
|
142
|
-
</>
|
|
143
|
-
)}
|
|
144
|
-
</button>
|
|
145
|
-
</div>
|
|
115
|
+
<button
|
|
116
|
+
className={`code-block-copy ${copied ? "copied" : ""}`}
|
|
117
|
+
onClick={handleCopy}
|
|
118
|
+
type="button"
|
|
119
|
+
aria-label="Copy code"
|
|
120
|
+
>
|
|
121
|
+
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
122
|
+
</button>
|
|
146
123
|
<pre>
|
|
147
124
|
<code>
|
|
148
125
|
<span className="line">{activeCommand}</span>
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import React, { useState, Children, isValidElement, useRef } from "react";
|
|
2
|
+
import { CodeBlock } from "../CodeBlock";
|
|
3
|
+
import { NPM } from "../../icons/npm";
|
|
4
|
+
import { Pnpm } from "../../icons/pnpm";
|
|
5
|
+
import { Bun } from "../../icons/bun";
|
|
6
|
+
import { Deno } from "../../icons/deno";
|
|
7
|
+
import { Yarn } from "../../icons/yarn";
|
|
2
8
|
|
|
3
9
|
/* ─── Tab (individual panel) ──────────────────────────────── */
|
|
4
10
|
export interface TabProps {
|
|
@@ -15,7 +21,17 @@ export interface TabProps {
|
|
|
15
21
|
* ```
|
|
16
22
|
*/
|
|
17
23
|
export function Tab({ children }: TabProps) {
|
|
18
|
-
|
|
24
|
+
// If children is a simple string, wrap it in a CodeBlock for syntax highlighting
|
|
25
|
+
const content =
|
|
26
|
+
typeof children === "string" ? (
|
|
27
|
+
<CodeBlock className="language-bash">
|
|
28
|
+
<code>{children.trim()}</code>
|
|
29
|
+
</CodeBlock>
|
|
30
|
+
) : (
|
|
31
|
+
children
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return <div className="ld-tab-panel">{content}</div>;
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
/* ─── Tabs (container) ────────────────────────────────────── */
|
|
@@ -25,6 +41,16 @@ export interface TabsProps {
|
|
|
25
41
|
children: React.ReactNode;
|
|
26
42
|
}
|
|
27
43
|
|
|
44
|
+
const getIconForLabel = (label: string) => {
|
|
45
|
+
const l = label.toLowerCase();
|
|
46
|
+
if (l.includes("npm")) return <NPM />;
|
|
47
|
+
if (l.includes("pnpm")) return <Pnpm />;
|
|
48
|
+
if (l.includes("yarn")) return <Yarn />;
|
|
49
|
+
if (l.includes("bun")) return <Bun />;
|
|
50
|
+
if (l.includes("deno")) return <Deno />;
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
|
|
28
54
|
/**
|
|
29
55
|
* Tab container that manages active state.
|
|
30
56
|
*
|
|
@@ -64,6 +90,7 @@ export function Tabs({ defaultIndex = 0, children }: TabsProps) {
|
|
|
64
90
|
<div className="ld-tabs__bar" role="tablist" onKeyDown={handleKeyDown}>
|
|
65
91
|
{tabs.map((child, i) => {
|
|
66
92
|
const label = (child as React.ReactElement<TabProps>).props.label;
|
|
93
|
+
const Icon = getIconForLabel(label);
|
|
67
94
|
return (
|
|
68
95
|
<button
|
|
69
96
|
key={i}
|
|
@@ -72,11 +99,16 @@ export function Tabs({ defaultIndex = 0, children }: TabsProps) {
|
|
|
72
99
|
aria-controls={`tabpanel-${i}`}
|
|
73
100
|
id={`tab-${i}`}
|
|
74
101
|
tabIndex={i === active ? 0 : -1}
|
|
75
|
-
ref={(el) => {
|
|
76
|
-
|
|
102
|
+
ref={(el) => {
|
|
103
|
+
tabRefs.current[i] = el;
|
|
104
|
+
}}
|
|
105
|
+
className={`ld-tabs__trigger ${
|
|
106
|
+
i === active ? "ld-tabs__trigger--active" : ""
|
|
107
|
+
}`}
|
|
77
108
|
onClick={() => setActive(i)}
|
|
78
109
|
>
|
|
79
|
-
{
|
|
110
|
+
{Icon}
|
|
111
|
+
<span>{label}</span>
|
|
80
112
|
</button>
|
|
81
113
|
);
|
|
82
114
|
})}
|
|
@@ -276,6 +276,9 @@
|
|
|
276
276
|
|
|
277
277
|
.ld-tabs__trigger {
|
|
278
278
|
position: relative;
|
|
279
|
+
display: flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
gap: 0.5rem;
|
|
279
282
|
padding: 0.65rem 1.25rem;
|
|
280
283
|
background: none;
|
|
281
284
|
border: none;
|
|
@@ -288,6 +291,11 @@
|
|
|
288
291
|
transition: all 0.2s ease;
|
|
289
292
|
white-space: nowrap;
|
|
290
293
|
}
|
|
294
|
+
|
|
295
|
+
.ld-tabs__trigger svg {
|
|
296
|
+
width: 1rem;
|
|
297
|
+
height: 1rem;
|
|
298
|
+
}
|
|
291
299
|
.ld-tabs__trigger:hover {
|
|
292
300
|
color: var(--ld-text-main);
|
|
293
301
|
background-color: rgba(255, 255, 255, 0.03);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
const Yarn = (props: SVGProps<SVGSVGElement>) => (
|
|
4
|
+
<svg {...props} viewBox="0 0 256 256">
|
|
5
|
+
<path
|
|
6
|
+
fill="#2C8EBB"
|
|
7
|
+
d="M128 0C57.307 0 0 57.307 0 128s57.307 128 128 128 128-57.307 128-128S198.693 0 128 0zm0 234.667C69.195 234.667 21.333 186.805 21.333 128S69.195 21.333 128 21.333 234.667 69.195 234.667 128 186.805 234.667 128 234.667z"
|
|
8
|
+
/>
|
|
9
|
+
<path
|
|
10
|
+
fill="#2C8EBB"
|
|
11
|
+
d="M173.045 74.053c-4.632-4.632-12.144-4.632-16.776 0L128 102.323l-28.269-28.27c-4.632-4.632-12.144-4.632-16.776 0-4.632 4.632-4.632 12.144 0 16.776L111.224 119.1l-28.269 28.269c-4.632 4.632-4.632 12.144 0 16.776 2.316 2.316 5.352 3.474 8.388 3.474s6.072-1.158 8.388-3.474L128 135.877l28.269 28.268c2.316 2.316 5.352 3.474 8.388 3.474s6.072-1.158 8.388-3.474c4.632-4.632 4.632-12.144 0-16.776L144.776 119.1l28.269-28.271 c4.632-4.632 4.632-12.144 0-16.776z"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export { Yarn };
|
|
@@ -206,55 +206,52 @@
|
|
|
206
206
|
overflow: hidden;
|
|
207
207
|
border: 1px solid var(--ld-border-subtle);
|
|
208
208
|
background-color: var(--ld-code-bg);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.code-block-header {
|
|
212
|
-
display: flex;
|
|
213
|
-
align-items: center;
|
|
214
|
-
justify-content: space-between;
|
|
215
|
-
padding: 0.5rem 1.25rem;
|
|
216
|
-
background-color: var(--ld-code-header);
|
|
217
|
-
border-bottom: 1px solid var(--ld-border-subtle);
|
|
218
|
-
font-size: 0.75rem;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.code-block-lang {
|
|
222
|
-
color: var(--ld-text-dim);
|
|
223
|
-
font-family: var(--ld-font-mono);
|
|
224
|
-
font-weight: 500;
|
|
225
|
-
text-transform: uppercase;
|
|
226
|
-
letter-spacing: 0.05em;
|
|
209
|
+
position: relative;
|
|
227
210
|
}
|
|
228
211
|
|
|
229
212
|
.code-block-copy {
|
|
230
213
|
display: flex;
|
|
231
214
|
align-items: center;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
215
|
+
justify-content: center;
|
|
216
|
+
position: absolute;
|
|
217
|
+
top: 0.75rem;
|
|
218
|
+
right: 0.75rem;
|
|
219
|
+
z-index: 50;
|
|
220
|
+
padding: 0.4rem;
|
|
221
|
+
background-color: rgba(20, 20, 30, 0.8);
|
|
222
|
+
backdrop-filter: blur(8px);
|
|
223
|
+
-webkit-backdrop-filter: blur(8px);
|
|
235
224
|
border: 1px solid var(--ld-border-subtle);
|
|
236
|
-
border-radius:
|
|
225
|
+
border-radius: 6px;
|
|
237
226
|
color: var(--ld-text-dim);
|
|
238
|
-
font-family: var(--ld-font-sans);
|
|
239
|
-
font-size: 0.6875rem;
|
|
240
227
|
cursor: pointer;
|
|
241
|
-
transition: all 0.2s;
|
|
228
|
+
transition: all 0.2s ease;
|
|
229
|
+
opacity: 0;
|
|
230
|
+
visibility: hidden;
|
|
231
|
+
pointer-events: none;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.code-block-wrapper:hover .code-block-copy {
|
|
235
|
+
opacity: 1;
|
|
236
|
+
visibility: visible;
|
|
237
|
+
pointer-events: auto;
|
|
242
238
|
}
|
|
243
239
|
|
|
244
240
|
.code-block-copy:hover {
|
|
245
241
|
color: var(--ld-text-main);
|
|
246
242
|
border-color: var(--ld-border-strong);
|
|
247
|
-
background-color: rgba(255, 255, 255, 0.
|
|
243
|
+
background-color: rgba(255, 255, 255, 0.08);
|
|
248
244
|
}
|
|
249
245
|
|
|
250
246
|
.code-block-copy.copied {
|
|
251
247
|
color: #22c55e;
|
|
252
|
-
border-color: rgba(34, 197, 94, 0.
|
|
248
|
+
border-color: rgba(34, 197, 94, 0.4);
|
|
249
|
+
opacity: 1;
|
|
253
250
|
}
|
|
254
251
|
|
|
255
252
|
.code-block-copy svg {
|
|
256
|
-
width:
|
|
257
|
-
height:
|
|
253
|
+
width: 16px;
|
|
254
|
+
height: 16px;
|
|
258
255
|
}
|
|
259
256
|
|
|
260
257
|
.code-block-wrapper pre {
|
package/src/client/utils.ts
CHANGED
|
@@ -24,3 +24,26 @@ const formatStars = (count: number): string => {
|
|
|
24
24
|
compactDisplay: "short",
|
|
25
25
|
}).format(count);
|
|
26
26
|
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Copy text to clipboard.
|
|
30
|
+
* @param text - The text to copy.
|
|
31
|
+
* @returns True if the text was copied successfully.
|
|
32
|
+
*/
|
|
33
|
+
export const copyToClipboard = async (text: string) => {
|
|
34
|
+
try {
|
|
35
|
+
await navigator.clipboard.writeText(text);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
// Fallback
|
|
39
|
+
const textarea = document.createElement("textarea");
|
|
40
|
+
textarea.value = text;
|
|
41
|
+
textarea.style.position = "fixed";
|
|
42
|
+
textarea.style.opacity = "0";
|
|
43
|
+
document.body.appendChild(textarea);
|
|
44
|
+
textarea.select();
|
|
45
|
+
document.execCommand("copy");
|
|
46
|
+
document.body.removeChild(textarea);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
};
|